mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-14 19:18:10 +02:00
Implement the capture command building infrastructure ported from python-validity's sensor.py and timeslot.py. This provides all the algorithms needed to construct sensor capture commands for calibration, enrollment, and identification modes. New components: - TLV chunk parsing (split/merge) for capture programs - Timeslot DSP instruction decoder (16 opcodes, 1-3 bytes each) - Timeslot table patching (Call repeat multiplication, factory cal) - Line Update Type 1 algorithm for 0xb5-class sensors - build_cmd_02(): main capture command builder - Factory bits parsing (subtag 3 cal values, subtag 7 cal data) - Frame averaging with multi-line deinterlacing - Calibration data processing (scale/accumulate/clip) - Clean slate format with SHA256 verification - Bitpack compression for factory calibration values - Finger ID mapping (FpFinger <-> VCSFW subtype 1-10) - LED control commands (glow_start_scan, glow_end_scan) - CaptureProg lookup table for firmware 6.x type-1 devices - OPEN_CAPTURE_SETUP state in the open SSM 27 unit tests covering all components. Full test suite: 36 OK, 0 Fail, 2 Skipped.
1019 lines
34 KiB
C
1019 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 128 bytes (LED control payload) */
|
|
g_assert_cmpuint (start_len, ==, 128);
|
|
g_assert_cmpuint (end_len, ==, 128);
|
|
|
|
/* 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 ();
|
|
}
|