libfprint/tests/test-validity-capture.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

1024 lines
34 KiB
C

/*
* Unit tests for validity capture infrastructure
*
* 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_capture.h"
#include "drivers/validity/validity_sensor.h"
/* ================================================================
* T5.1: test_split_chunks_basic
*
* Verify that split_chunks correctly parses a TLV buffer with two
* known chunks and produces the right type, size, and data.
* ================================================================ */
static void
test_split_chunks_basic (void)
{
/* Build two TLV chunks:
* type=0x002a, size=4, data={0xAA,0xBB,0xCC,0xDD}
* type=0x0034, size=2, data={0x11,0x22}
*/
guint8 buf[] = {
0x2a, 0x00, 0x04, 0x00, 0xAA, 0xBB, 0xCC, 0xDD,
0x34, 0x00, 0x02, 0x00, 0x11, 0x22,
};
gsize n = 0;
ValidityCaptureChunk *chunks;
chunks = validity_capture_split_chunks (buf, sizeof (buf), &n);
g_assert_nonnull (chunks);
g_assert_cmpuint (n, ==, 2);
g_assert_cmpuint (chunks[0].type, ==, 0x002a);
g_assert_cmpuint (chunks[0].size, ==, 4);
g_assert_cmpmem (chunks[0].data, 4, buf + 4, 4);
g_assert_cmpuint (chunks[1].type, ==, 0x0034);
g_assert_cmpuint (chunks[1].size, ==, 2);
g_assert_cmpmem (chunks[1].data, 2, buf + 12, 2);
validity_capture_chunks_free (chunks, n);
}
/* ================================================================
* T5.2: test_split_merge_roundtrip
*
* Verify that split then merge produces identical bytes.
* ================================================================ */
static void
test_split_merge_roundtrip (void)
{
guint8 buf[] = {
0x2a, 0x00, 0x08, 0x00,
0x20, 0x01, 0x01, 0x00, 0x10, 0x01, 0x00, 0x00,
0x29, 0x00, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00,
};
gsize n = 0;
ValidityCaptureChunk *chunks;
chunks = validity_capture_split_chunks (buf, sizeof (buf), &n);
g_assert_nonnull (chunks);
g_assert_cmpuint (n, ==, 2);
gsize merged_len = 0;
guint8 *merged = validity_capture_merge_chunks (chunks, n, &merged_len);
g_assert_nonnull (merged);
g_assert_cmpuint (merged_len, ==, sizeof (buf));
g_assert_cmpmem (merged, merged_len, buf, sizeof (buf));
g_free (merged);
validity_capture_chunks_free (chunks, n);
}
/* ================================================================
* T5.3: test_split_chunks_empty
*
* Verify empty input returns empty result.
* ================================================================ */
static void
test_split_chunks_empty (void)
{
gsize n = 99;
ValidityCaptureChunk *chunks;
chunks = validity_capture_split_chunks (NULL, 0, &n);
g_assert_null (chunks);
g_assert_cmpuint (n, ==, 0);
}
/* ================================================================
* T5.4: test_split_chunks_truncated
*
* Verify truncated chunk (size extends past end) returns NULL.
* ================================================================ */
static void
test_split_chunks_truncated (void)
{
/* type=0x0034, size=0x0008, but only 4 bytes of data follow */
guint8 buf[] = {
0x34, 0x00, 0x08, 0x00, 0x11, 0x22, 0x33, 0x44,
};
gsize n = 99;
ValidityCaptureChunk *chunks;
chunks = validity_capture_split_chunks (buf, sizeof (buf), &n);
g_assert_null (chunks);
g_assert_cmpuint (n, ==, 0);
}
/* ================================================================
* T5.5: test_decode_insn_noop
*
* Verify NOOP (0x00) decodes to opcode 0 with length 1.
* ================================================================ */
static void
test_decode_insn_noop (void)
{
guint8 data[] = { 0x00 };
guint8 opcode, len, n_ops;
guint32 operands[3];
g_assert_true (validity_capture_decode_insn (data, 1, &opcode, &len,
operands, &n_ops));
g_assert_cmpuint (opcode, ==, TST_OP_NOOP);
g_assert_cmpuint (len, ==, 1);
g_assert_cmpuint (n_ops, ==, 0);
}
/* ================================================================
* T5.6: test_decode_insn_call
*
* Verify Call instruction (0x10-0x17) decodes correctly with
* rx_inc, address, and repeat operands.
* ================================================================ */
static void
test_decode_insn_call (void)
{
/* Call: rx_inc=2, address=0x0a*4=0x28, repeat=8 */
guint8 data[] = { 0x12, 0x0a, 0x08 };
guint8 opcode, len, n_ops;
guint32 operands[3];
g_assert_true (validity_capture_decode_insn (data, 3, &opcode, &len,
operands, &n_ops));
g_assert_cmpuint (opcode, ==, TST_OP_CALL);
g_assert_cmpuint (len, ==, 3);
g_assert_cmpuint (n_ops, ==, 3);
g_assert_cmpuint (operands[0], ==, 2); /* rx_inc */
g_assert_cmpuint (operands[1], ==, 0x28); /* address = 0x0a << 2 */
g_assert_cmpuint (operands[2], ==, 8); /* repeat */
}
/* ================================================================
* T5.7: test_decode_insn_call_repeat_zero
*
* Verify Call with repeat byte 0x00 decodes to repeat=0x100.
* ================================================================ */
static void
test_decode_insn_call_repeat_zero (void)
{
guint8 data[] = { 0x10, 0x05, 0x00 };
guint8 opcode, len, n_ops;
guint32 operands[3];
g_assert_true (validity_capture_decode_insn (data, 3, &opcode, &len,
operands, &n_ops));
g_assert_cmpuint (opcode, ==, TST_OP_CALL);
g_assert_cmpuint (operands[2], ==, 0x100);
}
/* ================================================================
* T5.8: test_decode_insn_regwrite
*
* Verify Register Write (0x40-0x7f) decodes correctly:
* register address = (b0 & 0x3f) * 4 + 0x80002000
* value = u16 LE from bytes 1-2
* ================================================================ */
static void
test_decode_insn_regwrite (void)
{
/* b0=0x4f → reg = (0x0f)*4 + 0x80002000 = 0x8000203C, value=0x1234 */
guint8 data[] = { 0x4f, 0x34, 0x12 };
guint8 opcode, len, n_ops;
guint32 operands[3];
g_assert_true (validity_capture_decode_insn (data, 3, &opcode, &len,
operands, &n_ops));
g_assert_cmpuint (opcode, ==, TST_OP_REG_WRITE);
g_assert_cmpuint (len, ==, 3);
g_assert_cmpuint (n_ops, ==, 2);
g_assert_cmpuint (operands[0], ==, 0x8000203c);
g_assert_cmpuint (operands[1], ==, 0x1234);
}
/* ================================================================
* T5.9: test_decode_insn_enable_rx
*
* Verify Enable Rx (opcode 6) decodes as 2-byte instruction.
* ================================================================ */
static void
test_decode_insn_enable_rx (void)
{
guint8 data[] = { 0x06, 0x42 };
guint8 opcode, len, n_ops;
guint32 operands[3];
g_assert_true (validity_capture_decode_insn (data, 2, &opcode, &len,
operands, &n_ops));
g_assert_cmpuint (opcode, ==, TST_OP_ENABLE_RX);
g_assert_cmpuint (len, ==, 2);
g_assert_cmpuint (n_ops, ==, 1);
g_assert_cmpuint (operands[0], ==, 0x42);
}
/* ================================================================
* T5.10: test_decode_insn_sample
*
* Verify Sample (0x80-0xbf) decodes with two operands.
* ================================================================ */
static void
test_decode_insn_sample (void)
{
/* b0=0x8a → operand0 = (0x0a >> 3) & 7 = 1, operand1 = 0x0a & 7 = 2 */
guint8 data[] = { 0x8a };
guint8 opcode, len, n_ops;
guint32 operands[3];
g_assert_true (validity_capture_decode_insn (data, 1, &opcode, &len,
operands, &n_ops));
g_assert_cmpuint (opcode, ==, TST_OP_SAMPLE);
g_assert_cmpuint (len, ==, 1);
g_assert_cmpuint (n_ops, ==, 2);
g_assert_cmpuint (operands[0], ==, 1);
g_assert_cmpuint (operands[1], ==, 2);
}
/* ================================================================
* T5.11: test_find_nth_insn
*
* Verify finding the Nth instruction of a given opcode in a buffer.
* ================================================================ */
static void
test_find_nth_insn (void)
{
/* Buffer: NOOP, NOOP, Call(rx=0,addr=0x14,rep=1), NOOP */
guint8 data[] = {
0x00, /* NOOP at offset 0 */
0x00, /* NOOP at offset 1 */
0x10, 0x05, 0x01, /* Call at offset 2 */
0x00, /* NOOP at offset 5 */
};
/* 1st NOOP is at offset 0 */
g_assert_cmpint (validity_capture_find_nth_insn (data, sizeof (data),
TST_OP_NOOP, 1), ==, 0);
/* 2nd NOOP is at offset 1 */
g_assert_cmpint (validity_capture_find_nth_insn (data, sizeof (data),
TST_OP_NOOP, 2), ==, 1);
/* 3rd NOOP is at offset 5 */
g_assert_cmpint (validity_capture_find_nth_insn (data, sizeof (data),
TST_OP_NOOP, 3), ==, 5);
/* 1st Call is at offset 2 */
g_assert_cmpint (validity_capture_find_nth_insn (data, sizeof (data),
TST_OP_CALL, 1), ==, 2);
/* No 2nd Call */
g_assert_cmpint (validity_capture_find_nth_insn (data, sizeof (data),
TST_OP_CALL, 2), ==, -1);
}
/* ================================================================
* T5.12: test_find_nth_regwrite
*
* Verify finding a Register Write to a specific register address.
* ================================================================ */
static void
test_find_nth_regwrite (void)
{
/* Buffer: RegWrite(0x80002000, 0x55), RegWrite(0x8000203C, 0xAB) */
guint8 data[] = {
0x40, 0x55, 0x00, /* reg = 0x80002000, val = 0x0055 */
0x4f, 0xAB, 0x00, /* reg = 0x8000203C, val = 0x00AB */
};
/* Find 1st write to 0x8000203C → offset 3 */
g_assert_cmpint (validity_capture_find_nth_regwrite (data, sizeof (data),
0x8000203c, 1), ==, 3);
/* No 2nd write to 0x8000203C */
g_assert_cmpint (validity_capture_find_nth_regwrite (data, sizeof (data),
0x8000203c, 2), ==, -1);
/* Find 1st write to 0x80002000 → offset 0 */
g_assert_cmpint (validity_capture_find_nth_regwrite (data, sizeof (data),
0x80002000, 1), ==, 0);
}
/* ================================================================
* T5.13: test_patch_timeslot_table
*
* Verify that patch_timeslot_table multiplies Call repeat counts
* by the given multiplier.
* ================================================================ */
static void
test_patch_timeslot_table (void)
{
/* Call(rx=0, addr=0x14, repeat=3) followed by NOOP */
guint8 data[] = {
0x10, 0x05, 0x03, /* Call: repeat=3 */
0x00, /* NOOP */
};
/* Multiply by 2, with inc_address=TRUE */
g_assert_true (validity_capture_patch_timeslot_table (data, sizeof (data),
TRUE, 2));
/* repeat becomes 3*2=6 */
g_assert_cmpuint (data[2], ==, 6);
/* address byte incremented */
g_assert_cmpuint (data[1], ==, 6);
}
/* ================================================================
* T5.14: test_patch_timeslot_table_no_mult_for_repeat1
*
* Verify that Call instructions with repeat <= 1 are NOT multiplied.
* ================================================================ */
static void
test_patch_timeslot_table_no_mult_for_repeat1 (void)
{
guint8 data[] = {
0x10, 0x05, 0x01, /* Call: repeat=1 */
0x00,
};
g_assert_true (validity_capture_patch_timeslot_table (data, sizeof (data),
TRUE, 4));
/* repeat stays 1 (not multiplied because <= 1) */
g_assert_cmpuint (data[2], ==, 1);
/* address NOT incremented */
g_assert_cmpuint (data[1], ==, 5);
}
/* ================================================================
* T5.15: test_bitpack_uniform
*
* When all values are identical, bitpack returns v0=0 (0 bits),
* v1=the common value, and zero-length packed data.
* ================================================================ */
static void
test_bitpack_uniform (void)
{
guint8 values[] = { 0x42, 0x42, 0x42, 0x42 };
guint8 v0, v1;
gsize out_len;
guint8 *packed = validity_capture_bitpack (values, 4, &v0, &v1, &out_len);
g_assert_nonnull (packed);
g_assert_cmpuint (v0, ==, 0);
g_assert_cmpuint (v1, ==, 0x42);
g_assert_cmpuint (out_len, ==, 0);
g_free (packed);
}
/* ================================================================
* T5.16: test_bitpack_range
*
* Verify bitpack with a small range of values.
* Values [10, 11, 12, 13] → delta range=3, useful_bits=2.
* ================================================================ */
static void
test_bitpack_range (void)
{
guint8 values[] = { 10, 11, 12, 13 };
guint8 v0, v1;
gsize out_len;
guint8 *packed = validity_capture_bitpack (values, 4, &v0, &v1, &out_len);
g_assert_nonnull (packed);
g_assert_cmpuint (v0, ==, 2); /* 2 bits needed for max delta 3 */
g_assert_cmpuint (v1, ==, 10); /* minimum value */
/* 4 values * 2 bits = 8 bits = 1 byte */
g_assert_cmpuint (out_len, ==, 1);
/* Deltas: [0, 1, 2, 3]
* Packed little-endian: bits 0-1 = 0b00, bits 2-3 = 0b01,
* bits 4-5 = 0b10, bits 6-7 = 0b11
* Byte = 0b11100100 = 0xE4 */
g_assert_cmpuint (packed[0], ==, 0xE4);
g_free (packed);
}
/* ================================================================
* T5.17: test_factory_bits_parsing
*
* Verify parsing a synthetic factory bits response with subtag 3
* (calibration values) and subtag 7 (calibration data).
* ================================================================ */
static void
test_factory_bits_parsing (void)
{
/* Factory bits response format:
* wtf(4LE) entries(4LE)
* entry: ptr(4LE) length(2LE) tag(2LE) subtag(2LE) flags(2LE) data[length]
*/
guint8 cal_values[] = { 0xAA, 0xBB, 0xCC, 0xDD };
guint8 cal_data[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
/* Build response buffer */
GByteArray *resp = g_byte_array_new ();
guint8 hdr[8];
/* Header: wtf=0, entries=2 */
FP_WRITE_UINT32_LE (hdr, 0);
FP_WRITE_UINT32_LE (hdr + 4, 2);
g_byte_array_append (resp, hdr, 8);
/* Entry 1: subtag=3, calibration values (4-byte header + actual data) */
{
guint8 entry[12];
guint16 length = 4 + sizeof (cal_values); /* 4-byte header + data */
FP_WRITE_UINT32_LE (entry, 0); /* ptr */
FP_WRITE_UINT16_LE (entry + 4, length); /* length */
FP_WRITE_UINT16_LE (entry + 6, 0x0001); /* tag */
FP_WRITE_UINT16_LE (entry + 8, 3); /* subtag = 3 */
FP_WRITE_UINT16_LE (entry + 10, 0); /* flags */
g_byte_array_append (resp, entry, 12);
guint8 data_hdr[4] = { 0, 0, 0, 0 }; /* 4-byte header */
g_byte_array_append (resp, data_hdr, 4);
g_byte_array_append (resp, cal_values, sizeof (cal_values));
}
/* Entry 2: subtag=7, calibration data (4-byte header + actual data) */
{
guint8 entry[12];
guint16 length = 4 + sizeof (cal_data);
FP_WRITE_UINT32_LE (entry, 0);
FP_WRITE_UINT16_LE (entry + 4, length);
FP_WRITE_UINT16_LE (entry + 6, 0x0002);
FP_WRITE_UINT16_LE (entry + 8, 7); /* subtag = 7 */
FP_WRITE_UINT16_LE (entry + 10, 0);
g_byte_array_append (resp, entry, 12);
guint8 data_hdr[4] = { 0, 0, 0, 0 };
g_byte_array_append (resp, data_hdr, 4);
g_byte_array_append (resp, cal_data, sizeof (cal_data));
}
guint8 *out_cal_values = NULL, *out_cal_data = NULL;
gsize out_cal_values_len = 0, out_cal_data_len = 0;
gboolean ok = validity_capture_parse_factory_bits (
resp->data, resp->len,
&out_cal_values, &out_cal_values_len,
&out_cal_data, &out_cal_data_len);
g_assert_true (ok);
g_assert_nonnull (out_cal_values);
g_assert_cmpuint (out_cal_values_len, ==, sizeof (cal_values));
g_assert_cmpmem (out_cal_values, out_cal_values_len,
cal_values, sizeof (cal_values));
g_assert_nonnull (out_cal_data);
g_assert_cmpuint (out_cal_data_len, ==, sizeof (cal_data));
g_assert_cmpmem (out_cal_data, out_cal_data_len,
cal_data, sizeof (cal_data));
g_free (out_cal_values);
g_free (out_cal_data);
g_byte_array_free (resp, TRUE);
}
/* ================================================================
* T5.18: test_factory_bits_no_subtag3
*
* Verify that parsing fails when subtag 3 is missing.
* ================================================================ */
static void
test_factory_bits_no_subtag3 (void)
{
/* Build response with only subtag=7 (no subtag=3) */
guint8 buf[32];
FP_WRITE_UINT32_LE (buf, 0); /* wtf */
FP_WRITE_UINT32_LE (buf + 4, 1); /* entries=1 */
/* Entry: subtag=7, length=5 (4 hdr + 1 data) */
FP_WRITE_UINT32_LE (buf + 8, 0);
FP_WRITE_UINT16_LE (buf + 12, 5);
FP_WRITE_UINT16_LE (buf + 14, 0x0001);
FP_WRITE_UINT16_LE (buf + 16, 7); /* subtag=7, not 3 */
FP_WRITE_UINT16_LE (buf + 18, 0);
memset (buf + 20, 0, 5);
guint8 *cv = NULL;
gsize cv_len = 0;
gboolean ok = validity_capture_parse_factory_bits (buf, 25,
&cv, &cv_len,
NULL, NULL);
g_assert_false (ok);
g_assert_null (cv);
}
/* ================================================================
* T5.19: test_average_frames_interleave2
*
* Verify frame averaging with interleave_lines=2 (repeat_multiplier=2).
* With 2 interleaved lines per calibration line, each output line
* should be the average of 2 input lines.
* ================================================================ */
static void
test_average_frames_interleave2 (void)
{
guint16 bytes_per_line = 4;
guint16 lines_per_calibration_data = 2;
guint16 lines_per_frame = 4; /* 2 cal lines * 2 interleave */
guint8 calibration_frames = 1;
/* Single frame: 4 lines * 4 bytes = 16 bytes */
guint8 raw[] = {
10, 20, 30, 40, /* line 0 (cal line 0, interleave 0) */
20, 30, 40, 50, /* line 1 (cal line 0, interleave 1) */
30, 40, 50, 60, /* line 2 (cal line 1, interleave 0) */
40, 50, 60, 70, /* line 3 (cal line 1, interleave 1) */
};
gsize out_len = 0;
guint8 *result = validity_capture_average_frames (
raw, sizeof (raw),
lines_per_frame, bytes_per_line,
lines_per_calibration_data, calibration_frames,
&out_len);
g_assert_nonnull (result);
/* Output: 2 cal lines * 4 bytes = 8 bytes */
g_assert_cmpuint (out_len, ==, 8);
/* Cal line 0: avg of lines 0+1 → (10+20)/2=15, (20+30)/2=25, etc. */
g_assert_cmpuint (result[0], ==, 15);
g_assert_cmpuint (result[1], ==, 25);
g_assert_cmpuint (result[2], ==, 35);
g_assert_cmpuint (result[3], ==, 45);
/* Cal line 1: avg of lines 2+3 → (30+40)/2=35, (40+50)/2=45, etc. */
g_assert_cmpuint (result[4], ==, 35);
g_assert_cmpuint (result[5], ==, 45);
g_assert_cmpuint (result[6], ==, 55);
g_assert_cmpuint (result[7], ==, 65);
g_free (result);
}
/* ================================================================
* T5.20: test_clean_slate_roundtrip
*
* Verify that building a clean slate and then verifying it succeeds.
* ================================================================ */
static void
test_clean_slate_roundtrip (void)
{
guint8 test_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
gsize slate_len = 0;
guint8 *slate = validity_capture_build_clean_slate (test_data,
sizeof (test_data),
&slate_len);
g_assert_nonnull (slate);
g_assert_cmpuint (slate_len, >, 68);
/* Magic should be 0x5002 */
g_assert_cmpuint (FP_READ_UINT16_LE (slate), ==, 0x5002);
/* Verify should pass */
g_assert_true (validity_capture_verify_clean_slate (slate, slate_len));
/* Corrupt one byte and verify should fail */
slate[70] ^= 0xff;
g_assert_false (validity_capture_verify_clean_slate (slate, slate_len));
g_free (slate);
}
/* ================================================================
* T5.21: test_finger_mapping
*
* Verify all 10 finger mappings work in both directions.
* ================================================================ */
static void
test_finger_mapping (void)
{
/* FpFinger enum: LEFT_THUMB=1, ..., RIGHT_LITTLE=10 */
for (guint f = 1; f <= 10; f++)
{
guint16 subtype = validity_finger_to_subtype (f);
g_assert_cmpuint (subtype, ==, f);
gint back = validity_subtype_to_finger (subtype);
g_assert_cmpint (back, ==, (gint) f);
}
/* Out of range */
g_assert_cmpuint (validity_finger_to_subtype (0), ==, 0);
g_assert_cmpuint (validity_finger_to_subtype (11), ==, 0);
g_assert_cmpint (validity_subtype_to_finger (0), ==, -1);
g_assert_cmpint (validity_subtype_to_finger (11), ==, -1);
}
/* ================================================================
* T5.22: test_led_commands
*
* Verify LED start/end commands have correct format.
* ================================================================ */
static void
test_led_commands (void)
{
gsize start_len = 0, end_len = 0;
const guint8 *start_cmd = validity_capture_glow_start_cmd (&start_len);
const guint8 *end_cmd = validity_capture_glow_end_cmd (&end_len);
g_assert_nonnull (start_cmd);
g_assert_nonnull (end_cmd);
/* Both should be 125 bytes (LED control payload) */
g_assert_cmpuint (start_len, ==, 125);
g_assert_cmpuint (end_len, ==, 125);
/* Both should start with cmd byte 0x39 */
g_assert_cmpuint (start_cmd[0], ==, 0x39);
g_assert_cmpuint (end_cmd[0], ==, 0x39);
}
/* ================================================================
* T5.23: test_capture_prog_lookup
*
* Verify that CaptureProg lookup returns data for known devices
* and NULL for unknown ones.
* ================================================================ */
static void
test_capture_prog_lookup (void)
{
gsize len = 0;
/* Known: firmware 6.x, dev_type 0xb5 */
const guint8 *prog = validity_capture_prog_lookup (6, 7, 0x00b5, &len);
g_assert_nonnull (prog);
g_assert_cmpuint (len, >, 0);
/* The program should be parseable as TLV chunks */
gsize n_chunks = 0;
ValidityCaptureChunk *chunks = validity_capture_split_chunks (prog, len, &n_chunks);
g_assert_nonnull (chunks);
g_assert_cmpuint (n_chunks, >=, 4); /* At least ACM, CEM, TST, offset */
/* Check that we have the expected chunk types */
gboolean has_acm = FALSE, has_tst = FALSE, has_2d = FALSE;
for (gsize i = 0; i < n_chunks; i++)
{
if (chunks[i].type == 0x002a)
has_acm = TRUE;
if (chunks[i].type == CAPT_CHUNK_TIMESLOT_2D)
has_tst = TRUE;
if (chunks[i].type == CAPT_CHUNK_2D_PARAMS)
has_2d = TRUE;
}
g_assert_true (has_acm);
g_assert_true (has_tst);
g_assert_true (has_2d);
validity_capture_chunks_free (chunks, n_chunks);
/* Also check 0x0885 (same geometry) */
prog = validity_capture_prog_lookup (6, 0, 0x0885, &len);
g_assert_nonnull (prog);
/* Unknown: firmware 5.x */
prog = validity_capture_prog_lookup (5, 0, 0x00b5, &len);
g_assert_null (prog);
/* Unknown: dev_type not in type1 list */
prog = validity_capture_prog_lookup (6, 0, 0x1234, &len);
g_assert_null (prog);
}
/* ================================================================
* T5.24: test_capture_state_setup
*
* Verify that state setup correctly initializes all fields from
* sensor type info and factory bits.
* ================================================================ */
static void
test_capture_state_setup (void)
{
ValidityCaptureState state;
const ValiditySensorTypeInfo *type_info;
type_info = validity_sensor_type_info_lookup (0x00b5);
g_assert_nonnull (type_info);
/* Build minimal factory bits response with subtag 3 */
guint8 cal_vals[] = { 0x10, 0x20, 0x30 };
GByteArray *fb = g_byte_array_new ();
guint8 hdr[8];
FP_WRITE_UINT32_LE (hdr, 0);
FP_WRITE_UINT32_LE (hdr + 4, 1);
g_byte_array_append (fb, hdr, 8);
guint8 entry[12];
guint16 length = 4 + sizeof (cal_vals);
FP_WRITE_UINT32_LE (entry, 0);
FP_WRITE_UINT16_LE (entry + 4, length);
FP_WRITE_UINT16_LE (entry + 6, 1);
FP_WRITE_UINT16_LE (entry + 8, 3);
FP_WRITE_UINT16_LE (entry + 10, 0);
g_byte_array_append (fb, entry, 12);
guint8 data_hdr[4] = { 0 };
g_byte_array_append (fb, data_hdr, 4);
g_byte_array_append (fb, cal_vals, sizeof (cal_vals));
validity_capture_state_init (&state);
gboolean ok = validity_capture_state_setup (&state, type_info,
0x00b5, 6, 7,
fb->data, fb->len);
g_assert_true (ok);
g_assert_true (state.is_type1_device);
g_assert_cmpuint (state.bytes_per_line, ==, 0x78);
g_assert_cmpuint (state.lines_per_frame, ==, 112 * 2); /* 224 */
g_assert_cmpuint (state.key_calibration_line, ==, 56); /* 112/2 */
g_assert_cmpuint (state.calibration_frames, ==, 3);
g_assert_cmpuint (state.calibration_iterations, ==, 3);
g_assert_nonnull (state.factory_calibration_values);
g_assert_cmpuint (state.factory_calibration_values_len, ==, sizeof (cal_vals));
g_assert_cmpmem (state.factory_calibration_values,
state.factory_calibration_values_len,
cal_vals, sizeof (cal_vals));
g_assert_nonnull (state.capture_prog);
g_assert_cmpuint (state.capture_prog_len, >, 0);
validity_capture_state_clear (&state);
g_byte_array_free (fb, TRUE);
}
/* ================================================================
* T5.25: test_build_cmd_02_header
*
* Verify that build_cmd_02 produces the expected 5-byte header:
* cmd(0x02) | bytes_per_line(2LE) | req_lines(2LE) | chunks...
* ================================================================ */
static void
test_build_cmd_02_header (void)
{
ValidityCaptureState state;
const ValiditySensorTypeInfo *type_info;
type_info = validity_sensor_type_info_lookup (0x00b5);
g_assert_nonnull (type_info);
validity_capture_state_init (&state);
/* Minimal setup: just enough for build_cmd_02 */
gsize prog_len;
state.capture_prog = validity_capture_prog_lookup (6, 7, 0x00b5, &prog_len);
g_assert_nonnull (state.capture_prog);
state.capture_prog_len = prog_len;
state.is_type1_device = TRUE;
state.bytes_per_line = type_info->bytes_per_line;
state.lines_per_frame = 224;
state.calibration_frames = 3;
state.key_calibration_line = 56;
/* Need factory calibration values (even if empty) for line_update */
state.factory_calibration_values = g_malloc0 (112);
state.factory_calibration_values_len = 112;
gsize cmd_len = 0;
guint8 *cmd = validity_capture_build_cmd_02 (&state, type_info,
VALIDITY_CAPTURE_CALIBRATE,
&cmd_len);
g_assert_nonnull (cmd);
g_assert_cmpuint (cmd_len, >=, 5);
/* Byte 0: command = 0x02 */
g_assert_cmpuint (cmd[0], ==, 0x02);
/* Bytes 1-2: bytes_per_line = 0x0078 */
g_assert_cmpuint (FP_READ_UINT16_LE (cmd + 1), ==, 0x0078);
/* Bytes 3-4: req_lines for CALIBRATE = frames * lines_per_frame + 1 */
guint16 expected_lines = 3 * 224 + 1;
g_assert_cmpuint (FP_READ_UINT16_LE (cmd + 3), ==, expected_lines);
/* Remainder should be parseable as TLV chunks */
gsize n_chunks = 0;
ValidityCaptureChunk *chunks = validity_capture_split_chunks (
cmd + 5, cmd_len - 5, &n_chunks);
g_assert_nonnull (chunks);
g_assert_cmpuint (n_chunks, >=, 4);
validity_capture_chunks_free (chunks, n_chunks);
g_free (cmd);
/* Test IDENTIFY mode: req_lines should be 0 */
cmd = validity_capture_build_cmd_02 (&state, type_info,
VALIDITY_CAPTURE_IDENTIFY,
&cmd_len);
g_assert_nonnull (cmd);
g_assert_cmpuint (FP_READ_UINT16_LE (cmd + 3), ==, 0);
g_free (cmd);
g_free (state.factory_calibration_values);
}
/* ================================================================
* T5.26: test_calibration_processing
*
* Verify that process_calibration applies scale and accumulates.
* ================================================================ */
static void
test_calibration_processing (void)
{
guint16 bytes_per_line = 16;
/* Single line with 8-byte header + 8 bytes of data */
guint8 frame[16] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* header (untouched) */
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, /* data to scale */
};
guint8 *calib = NULL;
gsize calib_len = 0;
/* First call: initializes calib_data */
validity_capture_process_calibration (&calib, &calib_len,
frame, sizeof (frame),
bytes_per_line);
g_assert_nonnull (calib);
g_assert_cmpuint (calib_len, ==, 16);
/* Header bytes should be preserved */
g_assert_cmpuint (calib[0], ==, 0x00);
g_assert_cmpuint (calib[7], ==, 0x07);
/* Data bytes at 0x80: scale(0x80) = (0x80 - 0x80) * 10 / 0x22 = 0
* So all data bytes should be 0x00 */
for (int i = 8; i < 16; i++)
g_assert_cmpuint (calib[i], ==, 0x00);
/* Second call with same frame: accumulate */
validity_capture_process_calibration (&calib, &calib_len,
frame, sizeof (frame),
bytes_per_line);
/* add(0, 0) = 0, so data bytes still 0 */
for (int i = 8; i < 16; i++)
g_assert_cmpuint (calib[i], ==, 0x00);
g_free (calib);
}
/* ================================================================
* T5.27: test_capture_split_real_prog
*
* Parse the actual capture program for 0xb5 and verify
* expected chunks are present.
* ================================================================ */
static void
test_capture_split_real_prog (void)
{
gsize prog_len = 0;
const guint8 *prog = validity_capture_prog_lookup (6, 7, 0x00b5, &prog_len);
g_assert_nonnull (prog);
gsize n = 0;
ValidityCaptureChunk *chunks = validity_capture_split_chunks (prog, prog_len, &n);
g_assert_nonnull (chunks);
g_assert_cmpuint (n, ==, 6);
/* Expected order: 0x2a, 0x2c, 0x34, 0x2f, 0x29, 0x35 */
g_assert_cmpuint (chunks[0].type, ==, 0x002a);
g_assert_cmpuint (chunks[0].size, ==, 8);
g_assert_cmpuint (chunks[1].type, ==, 0x002c);
g_assert_cmpuint (chunks[1].size, ==, 40);
g_assert_cmpuint (chunks[2].type, ==, CAPT_CHUNK_TIMESLOT_2D);
g_assert_cmpuint (chunks[2].size, ==, 64);
g_assert_cmpuint (chunks[3].type, ==, CAPT_CHUNK_2D_PARAMS);
g_assert_cmpuint (chunks[3].size, ==, 4);
/* 2D value should be 112 (0x70) */
g_assert_cmpuint (FP_READ_UINT32_LE (chunks[3].data), ==, 112);
g_assert_cmpuint (chunks[4].type, ==, 0x0029);
g_assert_cmpuint (chunks[4].size, ==, 4);
g_assert_cmpuint (chunks[5].type, ==, 0x0035);
g_assert_cmpuint (chunks[5].size, ==, 4);
validity_capture_chunks_free (chunks, n);
}
/* ================================================================
* main
* ================================================================ */
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
/* Chunk parsing */
g_test_add_func ("/validity/capture/split-chunks-basic",
test_split_chunks_basic);
g_test_add_func ("/validity/capture/split-merge-roundtrip",
test_split_merge_roundtrip);
g_test_add_func ("/validity/capture/split-chunks-empty",
test_split_chunks_empty);
g_test_add_func ("/validity/capture/split-chunks-truncated",
test_split_chunks_truncated);
/* Timeslot instruction decoder */
g_test_add_func ("/validity/capture/decode-insn-noop",
test_decode_insn_noop);
g_test_add_func ("/validity/capture/decode-insn-call",
test_decode_insn_call);
g_test_add_func ("/validity/capture/decode-insn-call-repeat-zero",
test_decode_insn_call_repeat_zero);
g_test_add_func ("/validity/capture/decode-insn-regwrite",
test_decode_insn_regwrite);
g_test_add_func ("/validity/capture/decode-insn-enable-rx",
test_decode_insn_enable_rx);
g_test_add_func ("/validity/capture/decode-insn-sample",
test_decode_insn_sample);
/* Instruction search */
g_test_add_func ("/validity/capture/find-nth-insn",
test_find_nth_insn);
g_test_add_func ("/validity/capture/find-nth-regwrite",
test_find_nth_regwrite);
/* Timeslot patching */
g_test_add_func ("/validity/capture/patch-timeslot-table",
test_patch_timeslot_table);
g_test_add_func ("/validity/capture/patch-timeslot-no-mult-repeat1",
test_patch_timeslot_table_no_mult_for_repeat1);
/* Bitpack */
g_test_add_func ("/validity/capture/bitpack-uniform",
test_bitpack_uniform);
g_test_add_func ("/validity/capture/bitpack-range",
test_bitpack_range);
/* Factory bits */
g_test_add_func ("/validity/capture/factory-bits-parsing",
test_factory_bits_parsing);
g_test_add_func ("/validity/capture/factory-bits-no-subtag3",
test_factory_bits_no_subtag3);
/* Frame averaging */
g_test_add_func ("/validity/capture/average-frames-interleave2",
test_average_frames_interleave2);
/* Clean slate */
g_test_add_func ("/validity/capture/clean-slate-roundtrip",
test_clean_slate_roundtrip);
/* Finger mapping */
g_test_add_func ("/validity/capture/finger-mapping",
test_finger_mapping);
/* LED commands */
g_test_add_func ("/validity/capture/led-commands",
test_led_commands);
/* CaptureProg lookup */
g_test_add_func ("/validity/capture/prog-lookup",
test_capture_prog_lookup);
/* State setup */
g_test_add_func ("/validity/capture/state-setup",
test_capture_state_setup);
/* build_cmd_02 */
g_test_add_func ("/validity/capture/build-cmd-02-header",
test_build_cmd_02_header);
/* Calibration processing */
g_test_add_func ("/validity/capture/calibration-processing",
test_calibration_processing);
/* Real capture program parsing */
g_test_add_func ("/validity/capture/split-real-prog",
test_capture_split_real_prog);
return g_test_run ();
}