libfprint/tests/test-validity-enroll.c
Leonardo Francisco 08ef87a80c validity: fix code style (uncrustify) and test assertions
Apply uncrustify formatting to all validity driver and test files
to pass the CI test_indent check.

Fix two pre-existing test failures:
- test-validity-capture: LED command blobs are 125 bytes, not 128
- test-validity-enroll: add 2-byte length prefix to test data to
  match parser's expected format, fix empty-data assertion (parser
  returns FALSE for data_len < 2)

All 41 tests pass, 0 failures.
2026-04-10 22:18:43 +00:00

328 lines
12 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;
}
/* 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 ();
}