/* * 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 #include #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 (); }