libfprint/tests/test-validity-enroll.c
Leonardo Francisco 94bbb5fa2c validity: Iteration 8 — Final Polish
Add init_hardcoded and init_clean_slate transmission to the open SSM.
Four new states (OPEN_SEND/RECV_INIT_HARDCODED, OPEN_SEND/RECV_INIT_
CLEAN_SLATE) between GET_FW_INFO and UPLOAD_FWEXT, matching the
python-validity send_init() flow. init_hardcoded is always sent;
clean_slate only when fwext is not loaded. Skipped in emulation mode.

Remove dead crt_hardcoded[] (420 bytes, G_GNUC_UNUSED) from
validity_tls.c — this CA cert data now lives exclusively in
validity_pair_constants.inc.

Expose enrollment response parser for unit testing:
- EnrollmentUpdateResult struct and ENROLLMENT_MAGIC_LEN moved to
  validity.h
- parse_enrollment_update_response() and enrollment_update_result_clear()
  no longer static

Remove in-tree doc/ directory — documentation lives in
../validity-artifacts/docs/.

Tests: 9 new enrollment parser test cases, 0 regressions.
Result: 41 OK, 0 Fail, 2 Skipped.
2026-04-22 03:06:34 +00:00

292 lines
10 KiB
C

/*
* 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;
}
/* ================================================================
* 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);
g_assert_true (ok);
g_assert_null (result.header);
g_assert_null (result.template_data);
g_assert_null (result.tid);
}
/* ================================================================
* 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 *data = build_block (0, payload, sizeof (payload),
&block_len);
EnrollmentUpdateResult result;
gboolean ok = parse_enrollment_update_response (data, block_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 *data = build_block (1, payload, sizeof (payload),
&block_len);
EnrollmentUpdateResult result;
gboolean ok = parse_enrollment_update_response (data, block_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 *data = build_block (3, payload, sizeof (payload),
&block_len);
EnrollmentUpdateResult result;
gboolean ok = parse_enrollment_update_response (data, block_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 */
gsize total = tmpl_len + hdr_len + tid_len;
g_autofree guint8 *data = g_malloc (total);
memcpy (data, tmpl, tmpl_len);
memcpy (data + tmpl_len, hdr, hdr_len);
memcpy (data + tmpl_len + hdr_len, tid, tid_len);
EnrollmentUpdateResult result;
gboolean ok = parse_enrollment_update_response (data, total, &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)
{
guint8 payload[] = { 0xAA };
gsize block_len;
g_autofree guint8 *data = build_block (0, payload, sizeof (payload),
&block_len);
/* Pass data_len shorter than block_size so the block can't be read */
EnrollmentUpdateResult result;
gboolean ok = parse_enrollment_update_response (data, 10, &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 *data = build_block (42, payload, sizeof (payload),
&block_len);
EnrollmentUpdateResult result;
gboolean ok = parse_enrollment_update_response (data, block_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 *data = build_block (1, NULL, 0, &block_len);
EnrollmentUpdateResult result;
gboolean ok = parse_enrollment_update_response (data, block_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 ();
}