Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Driver for Egis Technology (LighTuning) Match-On-Chip sensors | ||
3 | * Originally authored 2023 by Joshua Grisham <josh@joshuagrisham.com> | ||
4 | * | ||
5 | * Portions of code and logic inspired from the elanmoc libfprint driver | ||
6 | * which is copyright (C) 2021 Elan Microelectronics Inc (see elanmoc.c) | ||
7 | * | ||
8 | * Based on original reverse-engineering work by Joshua Grisham. The protocol has | ||
9 | * been reverse-engineered from captures of the official Windows driver, and by | ||
10 | * testing commands on the sensor with a multiplatform Python prototype driver: | ||
11 | * https://github.com/joshuagrisham/galaxy-book2-pro-linux/tree/main/fingerprint/ | ||
12 | * | ||
13 | * This library is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU Lesser General Public | ||
15 | * License as published by the Free Software Foundation; either | ||
16 | * version 2.1 of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This library is distributed in the hope that it will be useful, | ||
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
21 | * Lesser General Public License for more details. | ||
22 | * | ||
23 | * You should have received a copy of the GNU Lesser General Public | ||
24 | * License along with this library; if not, write to the Free Software | ||
25 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
26 | */ | ||
27 | |||
28 | #define FP_COMPONENT "egismoc" | ||
29 | |||
30 | #include <stdio.h> | ||
31 | #include <glib.h> | ||
32 | #include <sys/param.h> | ||
33 | |||
34 | #include "drivers_api.h" | ||
35 | #include "fpi-byte-writer.h" | ||
36 | |||
37 | #include "egismoc.h" | ||
38 | |||
39 | struct _FpiDeviceEgisMoc | ||
40 | { | ||
41 | FpDevice parent; | ||
42 | FpiSsm *task_ssm; | ||
43 | FpiSsm *cmd_ssm; | ||
44 | FpiUsbTransfer *cmd_transfer; | ||
45 | GCancellable *interrupt_cancellable; | ||
46 | GPtrArray *enrolled_ids; | ||
47 | gint max_enroll_stages; | ||
48 | }; | ||
49 | |||
50 |
4/5✓ Branch 0 taken 120 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 120 times.
✓ Branch 3 taken 120 times.
✗ Branch 4 not taken.
|
768 | G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_DEVICE); |
51 | |||
52 | static const FpIdEntry egismoc_id_table[] = { | ||
53 | { .vid = 0x1c7a, .pid = 0x0582, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 }, | ||
54 | { .vid = 0x1c7a, .pid = 0x0583, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 }, | ||
55 | { .vid = 0x1c7a, .pid = 0x0586, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 }, | ||
56 | { .vid = 0x1c7a, .pid = 0x0587, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 }, | ||
57 | { .vid = 0x1c7a, .pid = 0x05a1, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE2 }, | ||
58 | { .vid = 0, .pid = 0, .driver_data = 0 } | ||
59 | }; | ||
60 | |||
61 | typedef void (*SynCmdMsgCallback) (FpDevice *device, | ||
62 | guchar *buffer_in, | ||
63 | gsize length_in, | ||
64 | GError *error); | ||
65 | |||
66 | typedef struct egismoc_command_data | ||
67 | { | ||
68 | SynCmdMsgCallback callback; | ||
69 | } CommandData; | ||
70 | |||
71 | typedef struct egismoc_enroll_print | ||
72 | { | ||
73 | FpPrint *print; | ||
74 | int stage; | ||
75 | } EnrollPrint; | ||
76 | |||
77 | static void | ||
78 | 163 | egismoc_finger_on_sensor_cb (FpiUsbTransfer *transfer, | |
79 | FpDevice *device, | ||
80 | gpointer userdata, | ||
81 | GError *error) | ||
82 | { | ||
83 | 163 | fp_dbg ("Finger on sensor callback"); | |
84 | 163 | fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); | |
85 | |||
86 |
1/2✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
|
163 | g_return_if_fail (transfer->ssm); |
87 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 163 times.
|
163 | if (error) |
88 | ✗ | fpi_ssm_mark_failed (transfer->ssm, error); | |
89 | else | ||
90 | 163 | fpi_ssm_next_state (transfer->ssm); | |
91 | } | ||
92 | |||
93 | static void | ||
94 | 163 | egismoc_wait_finger_on_sensor (FpiSsm *ssm, | |
95 | FpDevice *device) | ||
96 | { | ||
97 | 163 | fp_dbg ("Wait for finger on sensor"); | |
98 | 163 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
99 | |||
100 | 163 | g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device); | |
101 | |||
102 | 163 | fpi_usb_transfer_fill_interrupt (transfer, EGISMOC_EP_CMD_INTERRUPT_IN, | |
103 | EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH); | ||
104 | 163 | transfer->ssm = ssm; | |
105 | /* Interrupt on this device always returns 1 byte short; this is expected */ | ||
106 | 163 | transfer->short_is_error = FALSE; | |
107 | |||
108 | 163 | fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED); | |
109 | |||
110 | 163 | fpi_usb_transfer_submit (g_steal_pointer (&transfer), | |
111 | EGISMOC_USB_INTERRUPT_TIMEOUT, | ||
112 | self->interrupt_cancellable, | ||
113 | egismoc_finger_on_sensor_cb, | ||
114 | NULL); | ||
115 | 163 | } | |
116 | |||
117 | static gboolean | ||
118 | 179 | egismoc_validate_response_prefix (const guchar *buffer_in, | |
119 | const gsize buffer_in_len, | ||
120 | const guchar *valid_prefix, | ||
121 | const gsize valid_prefix_len) | ||
122 | { | ||
123 | 179 | const gboolean result = memcmp (buffer_in + | |
124 | (egismoc_read_prefix_len + | ||
125 | EGISMOC_CHECK_BYTES_LENGTH), | ||
126 | valid_prefix, | ||
127 | 179 | valid_prefix_len) == 0; | |
128 | |||
129 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 177 times.
|
181 | fp_dbg ("Response prefix valid: %s", result ? "yes" : "NO"); |
130 | 179 | return result; | |
131 | } | ||
132 | |||
133 | static gboolean | ||
134 | 188 | egismoc_validate_response_suffix (const guchar *buffer_in, | |
135 | const gsize buffer_in_len, | ||
136 | const guchar *valid_suffix, | ||
137 | const gsize valid_suffix_len) | ||
138 | { | ||
139 | 188 | const gboolean result = memcmp (buffer_in + (buffer_in_len - valid_suffix_len), | |
140 | valid_suffix, | ||
141 | 188 | valid_suffix_len) == 0; | |
142 | |||
143 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 162 times.
|
214 | fp_dbg ("Response suffix valid: %s", result ? "yes" : "NO"); |
144 | 188 | return result; | |
145 | } | ||
146 | |||
147 | static void | ||
148 | 64 | egismoc_task_ssm_done (FpiSsm *ssm, | |
149 | FpDevice *device, | ||
150 | GError *error) | ||
151 | { | ||
152 | 64 | fp_dbg ("Task SSM done"); | |
153 | 64 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
154 | |||
155 | /* task_ssm is going to be freed by completion of SSM */ | ||
156 |
2/4✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 64 times.
|
64 | g_assert (!self->task_ssm || self->task_ssm == ssm); |
157 | 64 | self->task_ssm = NULL; | |
158 | |||
159 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 4 times.
|
64 | g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref); |
160 | |||
161 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 60 times.
|
64 | if (error) |
162 | 4 | fpi_device_action_error (device, error); | |
163 | 64 | } | |
164 | |||
165 | static void | ||
166 | 386 | egismoc_task_ssm_next_state_cb (FpDevice *device, | |
167 | guchar *buffer_in, | ||
168 | gsize length_in, | ||
169 | GError *error) | ||
170 | { | ||
171 | 386 | fp_dbg ("Task SSM next state callback"); | |
172 | 386 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
173 | |||
174 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 386 times.
|
386 | if (error) |
175 | ✗ | fpi_ssm_mark_failed (self->task_ssm, error); | |
176 | else | ||
177 | 386 | fpi_ssm_next_state (self->task_ssm); | |
178 | 386 | } | |
179 | |||
180 | static void | ||
181 | 625 | egismoc_cmd_receive_cb (FpiUsbTransfer *transfer, | |
182 | FpDevice *device, | ||
183 | gpointer userdata, | ||
184 | GError *error) | ||
185 | { | ||
186 | 625 | g_autofree guchar *buffer = NULL; | |
187 | 625 | CommandData *data = userdata; | |
188 | 625 | SynCmdMsgCallback callback; | |
189 | 625 | gssize actual_length; | |
190 | |||
191 | 625 | fp_dbg ("Command receive callback"); | |
192 | |||
193 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 625 times.
|
625 | if (error) |
194 | { | ||
195 | ✗ | fpi_ssm_mark_failed (transfer->ssm, error); | |
196 | ✗ | return; | |
197 | } | ||
198 |
2/4✓ Branch 0 taken 625 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 625 times.
|
625 | if (data == NULL || transfer->actual_length < egismoc_read_prefix_len) |
199 | { | ||
200 | ✗ | fpi_ssm_mark_failed (transfer->ssm, | |
201 | fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); | ||
202 | ✗ | return; | |
203 | } | ||
204 | |||
205 | /* Let's complete the previous ssm and then handle the callback, so that | ||
206 | * we are sure that we won't start a transfer or a new command while there is | ||
207 | * another one still ongoing | ||
208 | */ | ||
209 | 625 | callback = data->callback; | |
210 | 625 | buffer = g_steal_pointer (&transfer->buffer); | |
211 | 625 | actual_length = transfer->actual_length; | |
212 | |||
213 | 625 | fpi_ssm_mark_completed (transfer->ssm); | |
214 | |||
215 |
1/2✓ Branch 0 taken 625 times.
✗ Branch 1 not taken.
|
625 | if (callback) |
216 | 625 | callback (device, buffer, actual_length, NULL); | |
217 | } | ||
218 | |||
219 | static void | ||
220 | 1250 | egismoc_cmd_run_state (FpiSsm *ssm, | |
221 | FpDevice *device) | ||
222 | { | ||
223 | 1250 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
224 | |||
225 | 2500 | g_autoptr(FpiUsbTransfer) transfer = NULL; | |
226 | |||
227 |
2/3✓ Branch 1 taken 625 times.
✓ Branch 2 taken 625 times.
✗ Branch 3 not taken.
|
1250 | switch (fpi_ssm_get_cur_state (ssm)) |
228 | { | ||
229 | 625 | case CMD_SEND: | |
230 |
1/2✓ Branch 0 taken 625 times.
✗ Branch 1 not taken.
|
625 | if (self->cmd_transfer) |
231 | { | ||
232 | 625 | self->cmd_transfer->ssm = ssm; | |
233 | 625 | fpi_usb_transfer_submit (g_steal_pointer (&self->cmd_transfer), | |
234 | EGISMOC_USB_SEND_TIMEOUT, | ||
235 | fpi_device_get_cancellable (device), | ||
236 | fpi_ssm_usb_transfer_cb, | ||
237 | NULL); | ||
238 | 625 | break; | |
239 | } | ||
240 | |||
241 | ✗ | fpi_ssm_next_state (ssm); | |
242 | ✗ | break; | |
243 | |||
244 | 625 | case CMD_GET: | |
245 | 625 | transfer = fpi_usb_transfer_new (device); | |
246 | 625 | transfer->ssm = ssm; | |
247 | 625 | fpi_usb_transfer_fill_bulk (transfer, EGISMOC_EP_CMD_IN, | |
248 | EGISMOC_USB_IN_RECV_LENGTH); | ||
249 | 625 | fpi_usb_transfer_submit (g_steal_pointer (&transfer), | |
250 | EGISMOC_USB_RECV_TIMEOUT, | ||
251 | fpi_device_get_cancellable (device), | ||
252 | egismoc_cmd_receive_cb, | ||
253 | fpi_ssm_get_data (ssm)); | ||
254 | 625 | break; | |
255 | } | ||
256 | 1250 | } | |
257 | |||
258 | static void | ||
259 | 625 | egismoc_cmd_ssm_done (FpiSsm *ssm, | |
260 | FpDevice *device, | ||
261 | GError *error) | ||
262 | { | ||
263 | 1250 | g_autoptr(GError) local_error = error; | |
264 | 625 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
265 | 625 | CommandData *data = fpi_ssm_get_data (ssm); | |
266 | |||
267 |
1/2✓ Branch 0 taken 625 times.
✗ Branch 1 not taken.
|
625 | g_assert (self->cmd_ssm == ssm); |
268 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 625 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
625 | g_assert (!self->cmd_transfer || self->cmd_transfer->ssm == ssm); |
269 | |||
270 | 625 | self->cmd_ssm = NULL; | |
271 | 625 | self->cmd_transfer = NULL; | |
272 | |||
273 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 625 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
625 | if (error && data && data->callback) |
274 | ✗ | data->callback (device, NULL, 0, g_steal_pointer (&local_error)); | |
275 | 625 | } | |
276 | |||
277 | /* | ||
278 | * Derive the 2 "check bytes" for write payloads | ||
279 | * 32-bit big-endian sum of all 16-bit words (including check bytes) MOD 0xFFFF | ||
280 | * should be 0, otherwise the device will reject the payload | ||
281 | */ | ||
282 | static guint16 | ||
283 | 625 | egismoc_get_check_bytes (FpiByteReader *reader) | |
284 | { | ||
285 | 625 | fp_dbg ("Get check bytes"); | |
286 | 625 | size_t sum_values = 0; | |
287 | 625 | guint16 val; | |
288 | |||
289 | 625 | fpi_byte_reader_set_pos (reader, 0); | |
290 | |||
291 |
2/2✓ Branch 2 taken 7195 times.
✓ Branch 3 taken 625 times.
|
7820 | while (fpi_byte_reader_get_uint16_be (reader, &val)) |
292 | 7195 | sum_values += val; | |
293 | |||
294 | 625 | return G_MAXUINT16 - (sum_values % G_MAXUINT16); | |
295 | } | ||
296 | |||
297 | static void | ||
298 | 625 | egismoc_exec_cmd (FpDevice *device, | |
299 | guchar *cmd, | ||
300 | const gsize cmd_length, | ||
301 | GDestroyNotify cmd_destroy, | ||
302 | SynCmdMsgCallback callback) | ||
303 | { | ||
304 | 625 | g_auto(FpiByteWriter) writer = {0}; | |
305 | 625 | g_autoptr(FpiUsbTransfer) transfer = NULL; | |
306 | 625 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
307 | 625 | g_autofree CommandData *data = NULL; | |
308 | 625 | gsize buffer_out_length = 0; | |
309 | 625 | gboolean written = TRUE; | |
310 | 625 | guint16 check_value; | |
311 | |||
312 | 625 | fp_dbg ("Execute command and get response"); | |
313 | |||
314 | /* | ||
315 | * buffer_out should be a fully composed command (with prefix, check bytes, etc) | ||
316 | * which looks like this: | ||
317 | * E G I S 00 00 00 01 {cb1} {cb2} {payload} | ||
318 | * where cb1 and cb2 are some check bytes generated by the | ||
319 | * egismoc_get_check_bytes() method and payload is what is passed via the cmd | ||
320 | * parameter | ||
321 | */ | ||
322 | 625 | buffer_out_length = egismoc_write_prefix_len | |
323 | + EGISMOC_CHECK_BYTES_LENGTH | ||
324 | 625 | + cmd_length; | |
325 | |||
326 | 625 | fpi_byte_writer_init_with_size (&writer, buffer_out_length + | |
327 | (buffer_out_length % 2 ? 1 : 0), TRUE); | ||
328 | |||
329 | /* Prefix */ | ||
330 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 625 times.
|
625 | written &= fpi_byte_writer_put_data (&writer, egismoc_write_prefix, |
331 | egismoc_write_prefix_len); | ||
332 | |||
333 | /* Check Bytes - leave them as 00 for now then later generate and copy over | ||
334 | * the real ones */ | ||
335 | 625 | written &= fpi_byte_writer_change_pos (&writer, EGISMOC_CHECK_BYTES_LENGTH); | |
336 | |||
337 | /* Command Payload */ | ||
338 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 625 times.
|
625 | written &= fpi_byte_writer_put_data (&writer, cmd, cmd_length); |
339 | |||
340 | /* Now fetch and set the "real" check bytes based on the currently | ||
341 | * assembled payload */ | ||
342 | 625 | check_value = egismoc_get_check_bytes (FPI_BYTE_READER (&writer)); | |
343 | 625 | fpi_byte_writer_set_pos (&writer, egismoc_write_prefix_len); | |
344 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 625 times.
|
625 | written &= fpi_byte_writer_put_uint16_be (&writer, check_value); |
345 | |||
346 | /* destroy cmd if requested */ | ||
347 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 585 times.
|
625 | if (cmd_destroy) |
348 |
1/2✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
|
40 | g_clear_pointer (&cmd, cmd_destroy); |
349 | |||
350 |
1/2✓ Branch 0 taken 625 times.
✗ Branch 1 not taken.
|
625 | g_assert (self->cmd_ssm == NULL); |
351 | 625 | self->cmd_ssm = fpi_ssm_new (device, | |
352 | egismoc_cmd_run_state, | ||
353 | CMD_STATES); | ||
354 | |||
355 | 625 | data = g_new0 (CommandData, 1); | |
356 | 625 | data->callback = callback; | |
357 | 625 | fpi_ssm_set_data (self->cmd_ssm, g_steal_pointer (&data), g_free); | |
358 | |||
359 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 625 times.
|
625 | if (!written) |
360 | { | ||
361 | ✗ | fpi_ssm_start (self->cmd_ssm, egismoc_cmd_ssm_done); | |
362 | ✗ | fpi_ssm_mark_failed (self->cmd_ssm, | |
363 | fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); | ||
364 | ✗ | return; | |
365 | } | ||
366 | |||
367 | 625 | transfer = fpi_usb_transfer_new (device); | |
368 | 625 | transfer->short_is_error = TRUE; | |
369 | 625 | transfer->ssm = self->cmd_ssm; | |
370 | |||
371 | 625 | fpi_usb_transfer_fill_bulk_full (transfer, | |
372 | EGISMOC_EP_CMD_OUT, | ||
373 | fpi_byte_writer_reset_and_get_data (&writer), | ||
374 | buffer_out_length, | ||
375 | g_free); | ||
376 | |||
377 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 625 times.
|
625 | g_assert (self->cmd_transfer == NULL); |
378 | 625 | self->cmd_transfer = g_steal_pointer (&transfer); | |
379 | 625 | fpi_ssm_start (self->cmd_ssm, egismoc_cmd_ssm_done); | |
380 | } | ||
381 | |||
382 | static void | ||
383 | 48 | egismoc_set_print_data (FpPrint *print, | |
384 | const gchar *device_print_id, | ||
385 | const gchar *user_id) | ||
386 | { | ||
387 | 48 | GVariant *print_id_var = NULL; | |
388 | 48 | GVariant *fpi_data = NULL; | |
389 | 96 | g_autofree gchar *fill_user_id = NULL; | |
390 | |||
391 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 40 times.
|
48 | if (user_id) |
392 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | fill_user_id = g_strdup (user_id); |
393 | else | ||
394 | 40 | fill_user_id = g_strndup (device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); | |
395 | |||
396 | 48 | fpi_print_fill_from_user_id (print, fill_user_id); | |
397 | |||
398 | 48 | fpi_print_set_type (print, FPI_PRINT_RAW); | |
399 | 48 | fpi_print_set_device_stored (print, TRUE); | |
400 | |||
401 | 48 | g_object_set (print, "description", fill_user_id, NULL); | |
402 | |||
403 | 48 | print_id_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, | |
404 | device_print_id, | ||
405 | EGISMOC_FINGERPRINT_DATA_SIZE, | ||
406 | sizeof (guchar)); | ||
407 | 48 | fpi_data = g_variant_new ("(@ay)", print_id_var); | |
408 | 48 | g_object_set (print, "fpi-data", fpi_data, NULL); | |
409 | 48 | } | |
410 | |||
411 | static GPtrArray * | ||
412 | 28 | egismoc_get_enrolled_prints (FpDevice *device) | |
413 | { | ||
414 | 28 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
415 | |||
416 | 28 | g_autoptr(GPtrArray) result = g_ptr_array_new_with_free_func (g_object_unref); | |
417 | |||
418 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | if (!self->enrolled_ids) |
419 | return g_steal_pointer (&result); | ||
420 | |||
421 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 28 times.
|
60 | for (guint i = 0; i < self->enrolled_ids->len; i++) |
422 | { | ||
423 | 32 | FpPrint *print = fp_print_new (device); | |
424 | 32 | egismoc_set_print_data (print, g_ptr_array_index (self->enrolled_ids, i), NULL); | |
425 | 32 | g_ptr_array_add (result, g_object_ref_sink (print)); | |
426 | } | ||
427 | |||
428 | return g_steal_pointer (&result); | ||
429 | } | ||
430 | |||
431 | static void | ||
432 | 60 | egismoc_list_fill_enrolled_ids_cb (FpDevice *device, | |
433 | guchar *buffer_in, | ||
434 | gsize length_in, | ||
435 | GError *error) | ||
436 | { | ||
437 | 60 | fp_dbg ("List callback"); | |
438 | 60 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
439 | |||
440 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
|
60 | if (error) |
441 | { | ||
442 | ✗ | fpi_ssm_mark_failed (self->task_ssm, error); | |
443 | ✗ | return; | |
444 | } | ||
445 | |||
446 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 60 times.
|
60 | g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref); |
447 | 60 | self->enrolled_ids = g_ptr_array_new_with_free_func (g_free); | |
448 | |||
449 | 60 | FpiByteReader reader; | |
450 | 60 | gboolean read = TRUE; | |
451 | |||
452 | 60 | fpi_byte_reader_init (&reader, buffer_in, length_in); | |
453 | |||
454 | 60 | read &= fpi_byte_reader_set_pos (&reader, EGISMOC_LIST_RESPONSE_PREFIX_SIZE); | |
455 | |||
456 | /* | ||
457 | * Each fingerprint ID will be returned in this response as a 32 byte array | ||
458 | * The other stuff in the payload is 16 bytes long, so if there is at least 1 | ||
459 | * print then the length should be at least 16+32=48 bytes long | ||
460 | */ | ||
461 |
1/2✓ Branch 0 taken 132 times.
✗ Branch 1 not taken.
|
132 | while (read) |
462 | { | ||
463 | 132 | const guint8 *data; | |
464 | 132 | g_autofree gchar *print_id = NULL; | |
465 | |||
466 |
2/2✓ Branch 1 taken 60 times.
✓ Branch 2 taken 72 times.
|
132 | read &= fpi_byte_reader_get_data (&reader, EGISMOC_FINGERPRINT_DATA_SIZE, |
467 | &data); | ||
468 |
2/2✓ Branch 0 taken 72 times.
✓ Branch 1 taken 60 times.
|
132 | if (!read) |
469 | break; | ||
470 | |||
471 | 72 | print_id = g_strndup ((gchar *) data, EGISMOC_FINGERPRINT_DATA_SIZE); | |
472 | 72 | fp_dbg ("Device fingerprint %0d: %.*s", self->enrolled_ids->len + 1, | |
473 | EGISMOC_FINGERPRINT_DATA_SIZE, print_id); | ||
474 | 72 | g_ptr_array_add (self->enrolled_ids, g_steal_pointer (&print_id)); | |
475 | } | ||
476 | |||
477 | 60 | fp_info ("Number of currently enrolled fingerprints on the device is %d", | |
478 | self->enrolled_ids->len); | ||
479 | |||
480 |
1/2✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
|
60 | if (self->task_ssm) |
481 | 60 | fpi_ssm_next_state (self->task_ssm); | |
482 | } | ||
483 | |||
484 | static void | ||
485 | 56 | egismoc_list_run_state (FpiSsm *ssm, | |
486 | FpDevice *device) | ||
487 | { | ||
488 | 112 | g_autoptr(GPtrArray) enrolled_prints = NULL; | |
489 | |||
490 |
2/3✓ Branch 1 taken 28 times.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
|
56 | switch (fpi_ssm_get_cur_state (ssm)) |
491 | { | ||
492 | 28 | case LIST_GET_ENROLLED_IDS: | |
493 | 28 | egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, | |
494 | egismoc_list_fill_enrolled_ids_cb); | ||
495 | 28 | break; | |
496 | |||
497 | 28 | case LIST_RETURN_ENROLLED_PRINTS: | |
498 | 28 | enrolled_prints = egismoc_get_enrolled_prints (device); | |
499 | 28 | fpi_device_list_complete (device, g_steal_pointer (&enrolled_prints), NULL); | |
500 | 28 | fpi_ssm_next_state (ssm); | |
501 | 28 | break; | |
502 | } | ||
503 | 56 | } | |
504 | |||
505 | static void | ||
506 | 28 | egismoc_list (FpDevice *device) | |
507 | { | ||
508 | 28 | fp_dbg ("List"); | |
509 | 28 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
510 | |||
511 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | g_assert (self->task_ssm == NULL); |
512 | 28 | self->task_ssm = fpi_ssm_new (device, | |
513 | egismoc_list_run_state, | ||
514 | LIST_STATES); | ||
515 | 28 | fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); | |
516 | 28 | } | |
517 | |||
518 | static guchar * | ||
519 | 12 | egismoc_get_delete_cmd (FpDevice *device, | |
520 | FpPrint *delete_print, | ||
521 | gsize *length_out) | ||
522 | { | ||
523 | 12 | fp_dbg ("Get delete command"); | |
524 | 12 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
525 | 24 | g_auto(FpiByteWriter) writer = {0}; | |
526 | 12 | g_autoptr(GVariant) print_data = NULL; | |
527 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
|
12 | g_autoptr(GVariant) print_data_id_var = NULL; |
528 | 12 | const guchar *print_data_id = NULL; | |
529 | 12 | gsize print_data_id_len = 0; | |
530 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
|
12 | g_autofree gchar *print_description = NULL; |
531 | 12 | g_autofree guchar *enrolled_print_id = NULL; | |
532 | 12 | g_autofree guchar *result = NULL; | |
533 | 12 | gboolean written = TRUE; | |
534 | |||
535 | /* | ||
536 | * The final command body should contain: | ||
537 | * 1) hard-coded 00 00 | ||
538 | * 2) 2-byte size indiciator, 20*Number deleted identifiers plus 7 in form of: | ||
539 | * num_to_delete * 0x20 + 0x07 | ||
540 | * Since max prints can be higher than 7 then this goes up to 2 bytes | ||
541 | * (e9 + 9 = 109) | ||
542 | * 3) Hard-coded prefix (cmd_delete_prefix) | ||
543 | * 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 7 | ||
544 | * (num_to_delete * 0x20) | ||
545 | * 5) All of the currently registered prints to delete in their 32-byte device | ||
546 | * identifiers (enrolled_list) | ||
547 | */ | ||
548 | |||
549 | 12 | int num_to_delete = 0; | |
550 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
|
12 | if (delete_print) |
551 | num_to_delete = 1; | ||
552 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | else if (self->enrolled_ids) |
553 | 8 | num_to_delete = self->enrolled_ids->len; | |
554 | |||
555 | 12 | const gsize body_length = sizeof (guchar) * EGISMOC_FINGERPRINT_DATA_SIZE * | |
556 | num_to_delete; | ||
557 | /* total_length is the 6 various bytes plus prefix and body payload */ | ||
558 | 12 | const gsize total_length = (sizeof (guchar) * 6) + cmd_delete_prefix_len + | |
559 | body_length; | ||
560 | |||
561 | /* pre-fill entire payload with 00s */ | ||
562 | 12 | fpi_byte_writer_init_with_size (&writer, total_length, TRUE); | |
563 | |||
564 | /* start with 00 00 (just move starting offset up by 2) */ | ||
565 | 12 | written &= fpi_byte_writer_set_pos (&writer, 2); | |
566 | |||
567 | /* Size Counter bytes */ | ||
568 | /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for | ||
569 | * when we go to the 2nd byte | ||
570 | * note this will not work in case any model ever supports more than 14 prints | ||
571 | * (assumed max is 10) */ | ||
572 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (num_to_delete > 7) |
573 | { | ||
574 | ✗ | written &= fpi_byte_writer_put_uint8 (&writer, 0x01); | |
575 | ✗ | written &= fpi_byte_writer_put_uint8 (&writer, ((num_to_delete - 8) * 0x20) + 0x07); | |
576 | } | ||
577 | else | ||
578 | { | ||
579 | /* first byte is 0x00, just skip it */ | ||
580 | 12 | written &= fpi_byte_writer_change_pos (&writer, 1); | |
581 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
|
12 | written &= fpi_byte_writer_put_uint8 (&writer, (num_to_delete * 0x20) + 0x07); |
582 | } | ||
583 | |||
584 | /* command prefix */ | ||
585 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
|
12 | written &= fpi_byte_writer_put_data (&writer, cmd_delete_prefix, |
586 | cmd_delete_prefix_len); | ||
587 | |||
588 | /* 2-bytes size logic for counter again */ | ||
589 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (num_to_delete > 7) |
590 | { | ||
591 | ✗ | written &= fpi_byte_writer_put_uint8 (&writer, 0x01); | |
592 | ✗ | written &= fpi_byte_writer_put_uint8 (&writer, (num_to_delete - 8) * 0x20); | |
593 | } | ||
594 | else | ||
595 | { | ||
596 | /* first byte is 0x00, just skip it */ | ||
597 | 12 | written &= fpi_byte_writer_change_pos (&writer, 1); | |
598 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
|
12 | written &= fpi_byte_writer_put_uint8 (&writer, num_to_delete * 0x20); |
599 | } | ||
600 | |||
601 | /* append desired 32-byte fingerprint IDs */ | ||
602 | /* if passed a delete_print then fetch its data from the FpPrint */ | ||
603 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
|
12 | if (delete_print) |
604 | { | ||
605 | 4 | g_object_get (delete_print, "description", &print_description, NULL); | |
606 | 4 | g_object_get (delete_print, "fpi-data", &print_data, NULL); | |
607 | |||
608 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (!g_variant_check_format_string (print_data, "(@ay)", FALSE)) |
609 | { | ||
610 | ✗ | fpi_ssm_mark_failed (self->task_ssm, | |
611 | fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); | ||
612 | ✗ | return NULL; | |
613 | } | ||
614 | |||
615 | 4 | g_variant_get (print_data, "(@ay)", &print_data_id_var); | |
616 | 4 | print_data_id = g_variant_get_fixed_array (print_data_id_var, | |
617 | &print_data_id_len, sizeof (guchar)); | ||
618 | |||
619 |
3/6✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
|
4 | if (!g_str_has_prefix (print_description, "FP")) |
620 | ✗ | fp_dbg ("Fingerprint '%s' was not created by libfprint; deleting anyway.", | |
621 | print_description); | ||
622 | |||
623 | 4 | fp_info ("Delete fingerprint %s (%s)", print_description, print_data_id); | |
624 | |||
625 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | written &= fpi_byte_writer_put_data (&writer, print_data_id, |
626 | EGISMOC_FINGERPRINT_DATA_SIZE); | ||
627 | } | ||
628 | /* Otherwise assume this is a "clear" - just loop through and append all enrolled IDs */ | ||
629 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | else if (self->enrolled_ids) |
630 | { | ||
631 |
3/4✓ Branch 0 taken 16 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
24 | for (guint i = 0; i < self->enrolled_ids->len && written; i++) |
632 | { | ||
633 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
|
16 | written &= fpi_byte_writer_put_data (&writer, |
634 | g_ptr_array_index (self->enrolled_ids, i), | ||
635 | EGISMOC_FINGERPRINT_DATA_SIZE); | ||
636 | } | ||
637 | } | ||
638 | |||
639 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | g_assert (written); |
640 | |||
641 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | if (length_out) |
642 | 12 | *length_out = total_length; | |
643 | |||
644 | 12 | return fpi_byte_writer_reset_and_get_data (&writer); | |
645 | } | ||
646 | |||
647 | static void | ||
648 | 12 | egismoc_delete_cb (FpDevice *device, | |
649 | guchar *buffer_in, | ||
650 | gsize length_in, | ||
651 | GError *error) | ||
652 | { | ||
653 | 12 | fp_dbg ("Delete callback"); | |
654 | 12 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
655 | |||
656 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (error) |
657 | { | ||
658 | ✗ | fpi_ssm_mark_failed (self->task_ssm, error); | |
659 | ✗ | return; | |
660 | } | ||
661 | |||
662 | /* Check that the read payload indicates "success" with the delete */ | ||
663 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | if (egismoc_validate_response_prefix (buffer_in, |
664 | length_in, | ||
665 | rsp_delete_success_prefix, | ||
666 | rsp_delete_success_prefix_len)) | ||
667 | { | ||
668 |
2/2✓ Branch 1 taken 8 times.
✓ Branch 2 taken 4 times.
|
12 | if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_CLEAR_STORAGE) |
669 | { | ||
670 | 8 | fpi_device_clear_storage_complete (device, NULL); | |
671 | 8 | fpi_ssm_next_state (self->task_ssm); | |
672 | } | ||
673 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | else if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE) |
674 | { | ||
675 | 4 | fpi_device_delete_complete (device, NULL); | |
676 | 4 | fpi_ssm_next_state (self->task_ssm); | |
677 | } | ||
678 | else | ||
679 | { | ||
680 | ✗ | fpi_ssm_mark_failed (self->task_ssm, | |
681 | fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, | ||
682 | "Unsupported delete action.")); | ||
683 | } | ||
684 | } | ||
685 | else | ||
686 | { | ||
687 | ✗ | fpi_ssm_mark_failed (self->task_ssm, | |
688 | fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, | ||
689 | "Delete print was not successful")); | ||
690 | } | ||
691 | } | ||
692 | |||
693 | static void | ||
694 | 24 | egismoc_delete_run_state (FpiSsm *ssm, | |
695 | FpDevice *device) | ||
696 | { | ||
697 | 48 | g_autofree guchar *payload = NULL; | |
698 | 24 | gsize payload_length = 0; | |
699 | |||
700 |
2/3✓ Branch 1 taken 12 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
|
24 | switch (fpi_ssm_get_cur_state (ssm)) |
701 | { | ||
702 | 12 | case DELETE_GET_ENROLLED_IDS: | |
703 | /* get enrolled_ids from device for use building delete payload below */ | ||
704 | 12 | egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, | |
705 | egismoc_list_fill_enrolled_ids_cb); | ||
706 | 12 | break; | |
707 | |||
708 | 12 | case DELETE_DELETE: | |
709 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 8 times.
|
12 | if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE) |
710 | 4 | payload = egismoc_get_delete_cmd (device, fpi_ssm_get_data (ssm), | |
711 | &payload_length); | ||
712 | else | ||
713 | 8 | payload = egismoc_get_delete_cmd (device, NULL, &payload_length); | |
714 | |||
715 | 12 | egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, | |
716 | g_free, egismoc_delete_cb); | ||
717 | 12 | break; | |
718 | } | ||
719 | 24 | } | |
720 | |||
721 | static void | ||
722 | 8 | egismoc_clear_storage (FpDevice *device) | |
723 | { | ||
724 | 8 | fp_dbg ("Clear storage"); | |
725 | 8 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
726 | |||
727 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | g_assert (self->task_ssm == NULL); |
728 | 8 | self->task_ssm = fpi_ssm_new (device, | |
729 | egismoc_delete_run_state, | ||
730 | DELETE_STATES); | ||
731 | 8 | fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); | |
732 | 8 | } | |
733 | |||
734 | static void | ||
735 | 4 | egismoc_delete (FpDevice *device) | |
736 | { | ||
737 | 4 | fp_dbg ("Delete"); | |
738 | 4 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
739 | 4 | FpPrint *delete_print = NULL; | |
740 | |||
741 | 4 | fpi_device_get_delete_data (device, &delete_print); | |
742 | |||
743 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | g_assert (self->task_ssm == NULL); |
744 | 4 | self->task_ssm = fpi_ssm_new (device, | |
745 | egismoc_delete_run_state, | ||
746 | DELETE_STATES); | ||
747 | /* the print is owned by libfprint during deletion task */ | ||
748 | 4 | fpi_ssm_set_data (self->task_ssm, delete_print, NULL); | |
749 | 4 | fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); | |
750 | 4 | } | |
751 | |||
752 | static void | ||
753 | 155 | egismoc_enroll_status_report (FpDevice *device, | |
754 | EnrollPrint *enroll_print, | ||
755 | EnrollStatus status, | ||
756 | GError *error) | ||
757 | { | ||
758 | 155 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
759 | |||
760 |
3/5✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 120 times.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
|
151 | switch (status) |
761 | { | ||
762 | ✗ | case ENROLL_STATUS_DEVICE_FULL: | |
763 | case ENROLL_STATUS_DUPLICATE: | ||
764 | 4 | fpi_ssm_mark_failed (self->task_ssm, error); | |
765 | ✗ | break; | |
766 | |||
767 | 23 | case ENROLL_STATUS_RETRY: | |
768 | 23 | fpi_device_enroll_progress (device, enroll_print->stage, NULL, error); | |
769 | 23 | break; | |
770 | |||
771 | 120 | case ENROLL_STATUS_PARTIAL_OK: | |
772 | 120 | enroll_print->stage++; | |
773 | 120 | fp_info ("Partial capture successful. Please touch the sensor again (%d/%d)", | |
774 | enroll_print->stage, | ||
775 | self->max_enroll_stages); | ||
776 | 120 | fpi_device_enroll_progress (device, enroll_print->stage, enroll_print->print, NULL); | |
777 | 120 | break; | |
778 | |||
779 | 8 | case ENROLL_STATUS_COMPLETE: | |
780 | 8 | fp_info ("Enrollment was successful!"); | |
781 | 8 | fpi_device_enroll_complete (device, g_object_ref (enroll_print->print), NULL); | |
782 | 8 | break; | |
783 | |||
784 | ✗ | default: | |
785 | ✗ | if (error) | |
786 | ✗ | fpi_ssm_mark_failed (self->task_ssm, error); | |
787 | else | ||
788 | ✗ | fpi_ssm_mark_failed (self->task_ssm, | |
789 | fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, | ||
790 | "Unknown error")); | ||
791 | } | ||
792 | 151 | } | |
793 | |||
794 | static void | ||
795 | 143 | egismoc_read_capture_cb (FpDevice *device, | |
796 | guchar *buffer_in, | ||
797 | gsize length_in, | ||
798 | GError *error) | ||
799 | { | ||
800 | 143 | fp_dbg ("Read capture callback"); | |
801 | 143 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
802 | 143 | EnrollPrint *enroll_print = fpi_ssm_get_data (self->task_ssm); | |
803 | |||
804 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 143 times.
|
143 | if (error) |
805 | { | ||
806 | ✗ | fpi_ssm_mark_failed (self->task_ssm, error); | |
807 | ✗ | return; | |
808 | } | ||
809 | |||
810 | /* Check that the read payload indicates "success" */ | ||
811 |
2/2✓ Branch 1 taken 142 times.
✓ Branch 2 taken 1 times.
|
143 | if (egismoc_validate_response_prefix (buffer_in, |
812 | length_in, | ||
813 | rsp_read_success_prefix, | ||
814 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 22 times.
|
142 | rsp_read_success_prefix_len) && |
815 | 142 | egismoc_validate_response_suffix (buffer_in, | |
816 | length_in, | ||
817 | rsp_read_success_suffix, | ||
818 | rsp_read_success_suffix_len)) | ||
819 | { | ||
820 | 120 | egismoc_enroll_status_report (device, enroll_print, | |
821 | ENROLL_STATUS_PARTIAL_OK, NULL); | ||
822 | } | ||
823 | else | ||
824 | { | ||
825 | /* If not success then the sensor can either report "off center" or "sensor is dirty" */ | ||
826 | |||
827 | /* "Off center" */ | ||
828 |
2/2✓ Branch 1 taken 22 times.
✓ Branch 2 taken 1 times.
|
23 | if (egismoc_validate_response_prefix (buffer_in, |
829 | length_in, | ||
830 | rsp_read_offcenter_prefix, | ||
831 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | rsp_read_offcenter_prefix_len) && |
832 | 22 | egismoc_validate_response_suffix (buffer_in, | |
833 | length_in, | ||
834 | rsp_read_offcenter_suffix, | ||
835 | rsp_read_offcenter_suffix_len)) | ||
836 | 22 | error = fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER); | |
837 | |||
838 | /* "Sensor is dirty" */ | ||
839 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | else if (egismoc_validate_response_prefix (buffer_in, |
840 | length_in, | ||
841 | rsp_read_dirty_prefix, | ||
842 | rsp_read_dirty_prefix_len)) | ||
843 | 1 | error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, | |
844 | "Your device is having trouble recognizing you. " | ||
845 | "Make sure your sensor is clean."); | ||
846 | |||
847 | else | ||
848 | ✗ | error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, | |
849 | "Unknown failure trying to read your finger. " | ||
850 | "Please try again."); | ||
851 | |||
852 | 23 | egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_RETRY, error); | |
853 | } | ||
854 | |||
855 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 135 times.
|
143 | if (enroll_print->stage == self->max_enroll_stages) |
856 | 8 | fpi_ssm_next_state (self->task_ssm); | |
857 | else | ||
858 | 135 | fpi_ssm_jump_to_state (self->task_ssm, ENROLL_CAPTURE_SENSOR_RESET); | |
859 | } | ||
860 | |||
861 | static void | ||
862 | 12 | egismoc_enroll_check_cb (FpDevice *device, | |
863 | guchar *buffer_in, | ||
864 | gsize length_in, | ||
865 | GError *error) | ||
866 | { | ||
867 | 12 | fp_dbg ("Enroll check callback"); | |
868 | 12 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
869 | |||
870 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (error) |
871 | { | ||
872 | ✗ | fpi_ssm_mark_failed (self->task_ssm, error); | |
873 | ✗ | return; | |
874 | } | ||
875 | |||
876 | /* Check that the read payload reports "not yet enrolled" */ | ||
877 |
2/2✓ Branch 1 taken 8 times.
✓ Branch 2 taken 4 times.
|
12 | if (egismoc_validate_response_suffix (buffer_in, |
878 | length_in, | ||
879 | rsp_check_not_yet_enrolled_suffix, | ||
880 | rsp_check_not_yet_enrolled_suffix_len)) | ||
881 | 8 | fpi_ssm_next_state (self->task_ssm); | |
882 | else | ||
883 | 4 | egismoc_enroll_status_report (device, NULL, ENROLL_STATUS_DUPLICATE, | |
884 | fpi_device_error_new (FP_DEVICE_ERROR_DATA_DUPLICATE)); | ||
885 | } | ||
886 | |||
887 | /* | ||
888 | * Builds the full "check" payload which includes identifiers for all | ||
889 | * fingerprints which currently should exist on the storage. This payload is | ||
890 | * used during both enrollment and verify actions. | ||
891 | */ | ||
892 | static guchar * | ||
893 | 20 | egismoc_get_check_cmd (FpDevice *device, | |
894 | gsize *length_out) | ||
895 | { | ||
896 | 20 | fp_dbg ("Get check command"); | |
897 | 20 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
898 | 40 | g_auto(FpiByteWriter) writer = {0}; | |
899 | 20 | g_autofree guchar *result = NULL; | |
900 | 20 | gboolean written = TRUE; | |
901 | |||
902 | /* | ||
903 | * The final command body should contain: | ||
904 | * 1) hard-coded 00 00 | ||
905 | * 2) 2-byte size indiciator, 20*Number enrolled identifiers plus 9 in form of: | ||
906 | * (enrolled_ids->len + 1) * 0x20 + 0x09 | ||
907 | * Since max prints can be higher than 7 then this goes up to 2 bytes | ||
908 | * (e9 + 9 = 109) | ||
909 | * 3) Hard-coded prefix (cmd_check_prefix) | ||
910 | * 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 9 | ||
911 | * ((enrolled_ids->len + 1) * 0x20) | ||
912 | * 5) Hard-coded 32 * 0x00 bytes | ||
913 | * 6) All of the currently registered prints in their 32-byte device identifiers | ||
914 | * (enrolled_list) | ||
915 | * 7) Hard-coded suffix (cmd_check_suffix) | ||
916 | */ | ||
917 | |||
918 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | g_assert (self->enrolled_ids); |
919 | 20 | const gsize body_length = sizeof (guchar) * self->enrolled_ids->len * | |
920 | EGISMOC_FINGERPRINT_DATA_SIZE; | ||
921 | |||
922 | /* prefix length can depend on the type */ | ||
923 | 20 | const gsize check_prefix_length = (fpi_device_get_driver_data (device) & | |
924 | EGISMOC_DRIVER_CHECK_PREFIX_TYPE2) ? | ||
925 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 15 times.
|
20 | cmd_check_prefix_type2_len : |
926 | cmd_check_prefix_type1_len; | ||
927 | |||
928 | /* total_length is the 6 various bytes plus all other prefixes/suffixes and | ||
929 | * the body payload */ | ||
930 | 20 | const gsize total_length = (sizeof (guchar) * 6) | |
931 | + check_prefix_length | ||
932 | + EGISMOC_CMD_CHECK_SEPARATOR_LENGTH | ||
933 | 20 | + body_length | |
934 | 20 | + cmd_check_suffix_len; | |
935 | |||
936 | /* pre-fill entire payload with 00s */ | ||
937 | 20 | fpi_byte_writer_init_with_size (&writer, total_length, TRUE); | |
938 | |||
939 | /* start with 00 00 (just move starting offset up by 2) */ | ||
940 | 20 | written &= fpi_byte_writer_set_pos (&writer, 2); | |
941 | |||
942 | /* Size Counter bytes */ | ||
943 | /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for | ||
944 | * when we go to the 2nd byte | ||
945 | * note this will not work in case any model ever supports more than 14 prints | ||
946 | * (assumed max is 10) */ | ||
947 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | if (self->enrolled_ids->len > 6) |
948 | { | ||
949 | ✗ | written &= fpi_byte_writer_put_uint8 (&writer, 0x01); | |
950 | ✗ | written &= fpi_byte_writer_put_uint8 (&writer, | |
951 | ((self->enrolled_ids->len - 7) * 0x20) | ||
952 | + 0x09); | ||
953 | } | ||
954 | else | ||
955 | { | ||
956 | /* first byte is 0x00, just skip it */ | ||
957 | 20 | written &= fpi_byte_writer_change_pos (&writer, 1); | |
958 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
|
20 | written &= fpi_byte_writer_put_uint8 (&writer, |
959 | ((self->enrolled_ids->len + 1) * 0x20) + | ||
960 | 0x09); | ||
961 | } | ||
962 | |||
963 | /* command prefix */ | ||
964 |
2/2✓ Branch 1 taken 5 times.
✓ Branch 2 taken 15 times.
|
20 | if (fpi_device_get_driver_data (device) & EGISMOC_DRIVER_CHECK_PREFIX_TYPE2) |
965 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
5 | written &= fpi_byte_writer_put_data (&writer, cmd_check_prefix_type2, |
966 | cmd_check_prefix_type2_len); | ||
967 | else | ||
968 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
|
15 | written &= fpi_byte_writer_put_data (&writer, cmd_check_prefix_type1, |
969 | cmd_check_prefix_type1_len); | ||
970 | |||
971 | /* 2-bytes size logic for counter again */ | ||
972 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | if (self->enrolled_ids->len > 6) |
973 | { | ||
974 | ✗ | written &= fpi_byte_writer_put_uint8 (&writer, 0x01); | |
975 | ✗ | written &= fpi_byte_writer_put_uint8 (&writer, | |
976 | (self->enrolled_ids->len - 7) * 0x20); | ||
977 | } | ||
978 | else | ||
979 | { | ||
980 | /* first byte is 0x00, just skip it */ | ||
981 | 20 | written &= fpi_byte_writer_change_pos (&writer, 1); | |
982 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
|
20 | written &= fpi_byte_writer_put_uint8 (&writer, |
983 | (self->enrolled_ids->len + 1) * 0x20); | ||
984 | } | ||
985 | |||
986 | /* add 00s "separator" to offset position */ | ||
987 | 20 | written &= fpi_byte_writer_change_pos (&writer, | |
988 | EGISMOC_CMD_CHECK_SEPARATOR_LENGTH); | ||
989 | |||
990 |
3/4✓ Branch 0 taken 16 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
36 | for (guint i = 0; i < self->enrolled_ids->len && written; i++) |
991 | { | ||
992 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
|
16 | written &= fpi_byte_writer_put_data (&writer, |
993 | g_ptr_array_index (self->enrolled_ids, i), | ||
994 | EGISMOC_FINGERPRINT_DATA_SIZE); | ||
995 | } | ||
996 | |||
997 | /* command suffix */ | ||
998 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
|
20 | written &= fpi_byte_writer_put_data (&writer, cmd_check_suffix, |
999 | cmd_check_suffix_len); | ||
1000 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | g_assert (written); |
1001 | |||
1002 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (length_out) |
1003 | 20 | *length_out = total_length; | |
1004 | |||
1005 | 20 | return fpi_byte_writer_reset_and_get_data (&writer); | |
1006 | } | ||
1007 | |||
1008 | static void | ||
1009 | 696 | egismoc_enroll_run_state (FpiSsm *ssm, | |
1010 | FpDevice *device) | ||
1011 | { | ||
1012 | 696 | g_auto(FpiByteWriter) writer = {0}; | |
1013 | 696 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1014 | 696 | EnrollPrint *enroll_print = fpi_ssm_get_data (ssm); | |
1015 | 696 | g_autofree guchar *payload = NULL; | |
1016 | 696 | gsize payload_length = 0; | |
1017 | 696 | g_autofree gchar *device_print_id = NULL; | |
1018 | 696 | g_autofree gchar *user_id = NULL; | |
1019 | |||
1020 |
16/17✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 12 times.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 12 times.
✓ Branch 8 taken 12 times.
✓ Branch 9 taken 8 times.
✓ Branch 10 taken 143 times.
✓ Branch 11 taken 143 times.
✓ Branch 12 taken 143 times.
✓ Branch 13 taken 143 times.
✓ Branch 14 taken 8 times.
✓ Branch 15 taken 8 times.
✓ Branch 16 taken 8 times.
✓ Branch 17 taken 8 times.
|
696 | switch (fpi_ssm_get_cur_state (ssm)) |
1021 | { | ||
1022 | 12 | case ENROLL_GET_ENROLLED_IDS: | |
1023 | /* get enrolled_ids from device for use in check stages below */ | ||
1024 | 12 | egismoc_exec_cmd (device, cmd_list, cmd_list_len, | |
1025 | NULL, egismoc_list_fill_enrolled_ids_cb); | ||
1026 | 12 | break; | |
1027 | |||
1028 | 12 | case ENROLL_CHECK_ENROLLED_NUM: | |
1029 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (self->enrolled_ids->len >= EGISMOC_MAX_ENROLL_NUM) |
1030 | { | ||
1031 | ✗ | egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_DEVICE_FULL, | |
1032 | fpi_device_error_new (FP_DEVICE_ERROR_DATA_FULL)); | ||
1033 | ✗ | return; | |
1034 | } | ||
1035 | 12 | fpi_ssm_next_state (ssm); | |
1036 | 12 | break; | |
1037 | |||
1038 | 12 | case ENROLL_SENSOR_RESET: | |
1039 | 12 | egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, | |
1040 | NULL, egismoc_task_ssm_next_state_cb); | ||
1041 | 12 | break; | |
1042 | |||
1043 | 12 | case ENROLL_SENSOR_ENROLL: | |
1044 | 12 | egismoc_exec_cmd (device, cmd_sensor_enroll, cmd_sensor_enroll_len, | |
1045 | NULL, egismoc_task_ssm_next_state_cb); | ||
1046 | 12 | break; | |
1047 | |||
1048 | 12 | case ENROLL_WAIT_FINGER: | |
1049 | 12 | egismoc_wait_finger_on_sensor (ssm, device); | |
1050 | 12 | break; | |
1051 | |||
1052 | 12 | case ENROLL_SENSOR_CHECK: | |
1053 | 12 | egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, | |
1054 | NULL, egismoc_task_ssm_next_state_cb); | ||
1055 | 12 | break; | |
1056 | |||
1057 | 12 | case ENROLL_CHECK: | |
1058 | 12 | payload = egismoc_get_check_cmd (device, &payload_length); | |
1059 | 12 | egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, | |
1060 | g_free, egismoc_enroll_check_cb); | ||
1061 | 12 | break; | |
1062 | |||
1063 | 8 | case ENROLL_START: | |
1064 | 8 | egismoc_exec_cmd (device, cmd_enroll_starting, cmd_enroll_starting_len, | |
1065 | NULL, egismoc_task_ssm_next_state_cb); | ||
1066 | 8 | break; | |
1067 | |||
1068 | 143 | case ENROLL_CAPTURE_SENSOR_RESET: | |
1069 | 143 | egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, | |
1070 | NULL, egismoc_task_ssm_next_state_cb); | ||
1071 | 143 | break; | |
1072 | |||
1073 | 143 | case ENROLL_CAPTURE_SENSOR_START_CAPTURE: | |
1074 | 143 | egismoc_exec_cmd (device, cmd_sensor_start_capture, cmd_sensor_start_capture_len, | |
1075 | NULL, | ||
1076 | egismoc_task_ssm_next_state_cb); | ||
1077 | 143 | break; | |
1078 | |||
1079 | 143 | case ENROLL_CAPTURE_WAIT_FINGER: | |
1080 | 143 | egismoc_wait_finger_on_sensor (ssm, device); | |
1081 | 143 | break; | |
1082 | |||
1083 | 143 | case ENROLL_CAPTURE_READ_RESPONSE: | |
1084 | 143 | egismoc_exec_cmd (device, cmd_read_capture, cmd_read_capture_len, | |
1085 | NULL, egismoc_read_capture_cb); | ||
1086 | 143 | break; | |
1087 | |||
1088 | 8 | case ENROLL_COMMIT_START: | |
1089 | 8 | egismoc_exec_cmd (device, cmd_commit_starting, cmd_commit_starting_len, | |
1090 | NULL, egismoc_task_ssm_next_state_cb); | ||
1091 | 8 | break; | |
1092 | |||
1093 | 8 | case ENROLL_COMMIT: | |
1094 | 8 | user_id = fpi_print_generate_user_id (enroll_print->print); | |
1095 | 8 | fp_dbg ("New fingerprint ID: %s", user_id); | |
1096 | |||
1097 | 8 | device_print_id = g_strndup (user_id, EGISMOC_FINGERPRINT_DATA_SIZE); | |
1098 | 8 | egismoc_set_print_data (enroll_print->print, device_print_id, user_id); | |
1099 | |||
1100 | 8 | fpi_byte_writer_init (&writer); | |
1101 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | if (!fpi_byte_writer_put_data (&writer, cmd_new_print_prefix, |
1102 | cmd_new_print_prefix_len)) | ||
1103 | { | ||
1104 | ✗ | fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); | |
1105 | ✗ | break; | |
1106 | } | ||
1107 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (!fpi_byte_writer_put_data (&writer, (guint8 *) device_print_id, |
1108 | EGISMOC_FINGERPRINT_DATA_SIZE)) | ||
1109 | { | ||
1110 | ✗ | fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); | |
1111 | ✗ | break; | |
1112 | } | ||
1113 | |||
1114 | 8 | payload_length = fpi_byte_writer_get_size (&writer); | |
1115 | 8 | egismoc_exec_cmd (device, fpi_byte_writer_reset_and_get_data (&writer), | |
1116 | payload_length, | ||
1117 | g_free, egismoc_task_ssm_next_state_cb); | ||
1118 | 8 | break; | |
1119 | |||
1120 | 8 | case ENROLL_COMMIT_SENSOR_RESET: | |
1121 | 8 | egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, | |
1122 | NULL, egismoc_task_ssm_next_state_cb); | ||
1123 | 8 | break; | |
1124 | |||
1125 | 8 | case ENROLL_COMPLETE: | |
1126 | 8 | egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_COMPLETE, NULL); | |
1127 | 8 | fpi_ssm_next_state (ssm); | |
1128 | 8 | break; | |
1129 | } | ||
1130 | } | ||
1131 | |||
1132 | static void | ||
1133 | 12 | egismoc_enroll (FpDevice *device) | |
1134 | { | ||
1135 | 12 | fp_dbg ("Enroll"); | |
1136 | 12 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1137 | 12 | EnrollPrint *enroll_print = g_new0 (EnrollPrint, 1); | |
1138 | |||
1139 | 12 | fpi_device_get_enroll_data (device, &enroll_print->print); | |
1140 | 12 | enroll_print->stage = 0; | |
1141 | |||
1142 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | g_assert (self->task_ssm == NULL); |
1143 | 12 | self->task_ssm = fpi_ssm_new (device, egismoc_enroll_run_state, ENROLL_STATES); | |
1144 | 12 | fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&enroll_print), g_free); | |
1145 | 12 | fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); | |
1146 | 12 | } | |
1147 | |||
1148 | static void | ||
1149 | 8 | egismoc_identify_check_cb (FpDevice *device, | |
1150 | guchar *buffer_in, | ||
1151 | gsize length_in, | ||
1152 | GError *error) | ||
1153 | { | ||
1154 | 8 | fp_dbg ("Identify check callback"); | |
1155 | 8 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1156 | 8 | gchar device_print_id[EGISMOC_FINGERPRINT_DATA_SIZE]; | |
1157 | 8 | FpPrint *print = NULL; | |
1158 | 8 | FpPrint *verify_print = NULL; | |
1159 | 8 | GPtrArray *prints; | |
1160 | 8 | gboolean found = FALSE; | |
1161 | 8 | guint index; | |
1162 | |||
1163 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (error) |
1164 | { | ||
1165 | ✗ | fpi_ssm_mark_failed (self->task_ssm, error); | |
1166 | ✗ | return; | |
1167 | } | ||
1168 | |||
1169 | /* Check that the read payload indicates "match" */ | ||
1170 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | if (egismoc_validate_response_suffix (buffer_in, |
1171 | length_in, | ||
1172 | rsp_identify_match_suffix, | ||
1173 | rsp_identify_match_suffix_len)) | ||
1174 | { | ||
1175 | /* | ||
1176 | On success, there is a 32 byte array of "something"(?) in chars 14-45 | ||
1177 | and then the 32 byte array ID of the matched print comes as chars 46-77 | ||
1178 | */ | ||
1179 | 8 | memcpy (device_print_id, | |
1180 | buffer_in + EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET, | ||
1181 | EGISMOC_FINGERPRINT_DATA_SIZE); | ||
1182 | |||
1183 | /* Create a new print from this device_print_id and then see if it matches | ||
1184 | * the one indicated | ||
1185 | */ | ||
1186 | 8 | print = fp_print_new (device); | |
1187 | 8 | egismoc_set_print_data (print, device_print_id, NULL); | |
1188 | |||
1189 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!print) |
1190 | { | ||
1191 | ✗ | fpi_ssm_mark_failed (self->task_ssm, | |
1192 | fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, | ||
1193 | "Failed to build a print from " | ||
1194 | "device response.")); | ||
1195 | ✗ | return; | |
1196 | } | ||
1197 | |||
1198 | 8 | fp_info ("Identify successful for: %s", fp_print_get_description (print)); | |
1199 | |||
1200 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
|
8 | if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY) |
1201 | { | ||
1202 | 4 | fpi_device_get_identify_data (device, &prints); | |
1203 | 4 | found = g_ptr_array_find_with_equal_func (prints, | |
1204 | print, | ||
1205 | (GEqualFunc) fp_print_equal, | ||
1206 | &index); | ||
1207 | |||
1208 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (found) |
1209 | 4 | fpi_device_identify_report (device, g_ptr_array_index (prints, index), print, NULL); | |
1210 | else | ||
1211 | ✗ | fpi_device_identify_report (device, NULL, print, NULL); | |
1212 | } | ||
1213 | else | ||
1214 | { | ||
1215 | 4 | fpi_device_get_verify_data (device, &verify_print); | |
1216 | 4 | fp_info ("Verifying against: %s", fp_print_get_description (verify_print)); | |
1217 | |||
1218 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | if (fp_print_equal (verify_print, print)) |
1219 | 4 | fpi_device_verify_report (device, FPI_MATCH_SUCCESS, print, NULL); | |
1220 | else | ||
1221 | ✗ | fpi_device_verify_report (device, FPI_MATCH_FAIL, print, NULL); | |
1222 | } | ||
1223 | } | ||
1224 | /* If device was successfully read but it was a "not matched" */ | ||
1225 | ✗ | else if (egismoc_validate_response_suffix (buffer_in, | |
1226 | length_in, | ||
1227 | rsp_identify_notmatch_suffix, | ||
1228 | rsp_identify_notmatch_suffix_len)) | ||
1229 | { | ||
1230 | ✗ | fp_info ("Print was not identified by the device"); | |
1231 | |||
1232 | ✗ | if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) | |
1233 | ✗ | fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL); | |
1234 | else | ||
1235 | ✗ | fpi_device_identify_report (device, NULL, NULL, NULL); | |
1236 | } | ||
1237 | else | ||
1238 | { | ||
1239 | ✗ | fpi_ssm_mark_failed (self->task_ssm, | |
1240 | fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, | ||
1241 | "Unrecognized response from device.")); | ||
1242 | ✗ | return; | |
1243 | } | ||
1244 | |||
1245 | 8 | fpi_ssm_next_state (self->task_ssm); | |
1246 | } | ||
1247 | |||
1248 | static void | ||
1249 | 72 | egismoc_identify_run_state (FpiSsm *ssm, | |
1250 | FpDevice *device) | ||
1251 | { | ||
1252 | 72 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1253 | 72 | g_autofree guchar *payload = NULL; | |
1254 | 72 | gsize payload_length = 0; | |
1255 | |||
1256 |
9/10✓ Branch 1 taken 8 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 8 times.
✓ Branch 7 taken 8 times.
✓ Branch 8 taken 8 times.
✓ Branch 9 taken 8 times.
✗ Branch 10 not taken.
|
72 | switch (fpi_ssm_get_cur_state (ssm)) |
1257 | { | ||
1258 | 8 | case IDENTIFY_GET_ENROLLED_IDS: | |
1259 | /* get enrolled_ids from device for use in check stages below */ | ||
1260 | 8 | egismoc_exec_cmd (device, cmd_list, cmd_list_len, | |
1261 | NULL, egismoc_list_fill_enrolled_ids_cb); | ||
1262 | 8 | break; | |
1263 | |||
1264 | 8 | case IDENTIFY_CHECK_ENROLLED_NUM: | |
1265 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (self->enrolled_ids->len == 0) |
1266 | { | ||
1267 | ✗ | fpi_ssm_mark_failed (g_steal_pointer (&self->task_ssm), | |
1268 | fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); | ||
1269 | ✗ | return; | |
1270 | } | ||
1271 | 8 | fpi_ssm_next_state (ssm); | |
1272 | 8 | break; | |
1273 | |||
1274 | 8 | case IDENTIFY_SENSOR_RESET: | |
1275 | 8 | egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, | |
1276 | NULL, egismoc_task_ssm_next_state_cb); | ||
1277 | 8 | break; | |
1278 | |||
1279 | 8 | case IDENTIFY_SENSOR_IDENTIFY: | |
1280 | 8 | egismoc_exec_cmd (device, cmd_sensor_identify, cmd_sensor_identify_len, | |
1281 | NULL, egismoc_task_ssm_next_state_cb); | ||
1282 | 8 | break; | |
1283 | |||
1284 | 8 | case IDENTIFY_WAIT_FINGER: | |
1285 | 8 | egismoc_wait_finger_on_sensor (ssm, device); | |
1286 | 8 | break; | |
1287 | |||
1288 | 8 | case IDENTIFY_SENSOR_CHECK: | |
1289 | 8 | egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, | |
1290 | NULL, egismoc_task_ssm_next_state_cb); | ||
1291 | 8 | break; | |
1292 | |||
1293 | 8 | case IDENTIFY_CHECK: | |
1294 | 8 | payload = egismoc_get_check_cmd (device, &payload_length); | |
1295 | 8 | egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, | |
1296 | g_free, egismoc_identify_check_cb); | ||
1297 | 8 | break; | |
1298 | |||
1299 | 8 | case IDENTIFY_COMPLETE_SENSOR_RESET: | |
1300 | 8 | egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, | |
1301 | NULL, egismoc_task_ssm_next_state_cb); | ||
1302 | 8 | break; | |
1303 | |||
1304 | /* | ||
1305 | * In Windows, the driver seems at this point to then immediately take | ||
1306 | * another read from the sensor; this is suspected to be an on-chip | ||
1307 | * "verify". However, because the user's finger is still on the sensor from | ||
1308 | * the identify, then it seems to always return positive. We will consider | ||
1309 | * this extra step unnecessary and just skip it in this driver. This driver | ||
1310 | * will instead handle matching of the FpPrint from the gallery in the | ||
1311 | * "verify" case of the callback egismoc_identify_check_cb. | ||
1312 | */ | ||
1313 | 8 | case IDENTIFY_COMPLETE: | |
1314 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
|
8 | if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY) |
1315 | 4 | fpi_device_identify_complete (device, NULL); | |
1316 | else | ||
1317 | 4 | fpi_device_verify_complete (device, NULL); | |
1318 | |||
1319 | 8 | fpi_ssm_mark_completed (ssm); | |
1320 | 8 | break; | |
1321 | } | ||
1322 | } | ||
1323 | |||
1324 | static void | ||
1325 | 8 | egismoc_identify_verify (FpDevice *device) | |
1326 | { | ||
1327 | 8 | fp_dbg ("Identify or Verify"); | |
1328 | 8 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1329 | |||
1330 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | g_assert (self->task_ssm == NULL); |
1331 | 8 | self->task_ssm = fpi_ssm_new (device, egismoc_identify_run_state, IDENTIFY_STATES); | |
1332 | 8 | fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); | |
1333 | 8 | } | |
1334 | |||
1335 | static void | ||
1336 | 4 | egismoc_fw_version_cb (FpDevice *device, | |
1337 | guchar *buffer_in, | ||
1338 | gsize length_in, | ||
1339 | GError *error) | ||
1340 | { | ||
1341 | 4 | fp_dbg ("Firmware version callback"); | |
1342 | 4 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1343 | 4 | g_autofree gchar *fw_version = NULL; | |
1344 | 4 | gsize prefix_length; | |
1345 | 4 | guchar *fw_version_start; | |
1346 | 4 | gsize fw_version_length; | |
1347 | |||
1348 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (error) |
1349 | { | ||
1350 | ✗ | fpi_ssm_mark_failed (self->task_ssm, error); | |
1351 | ✗ | return; | |
1352 | } | ||
1353 | |||
1354 | /* Check that the read payload indicates "success" */ | ||
1355 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (!egismoc_validate_response_suffix (buffer_in, |
1356 | length_in, | ||
1357 | rsp_fw_version_suffix, | ||
1358 | rsp_fw_version_suffix_len)) | ||
1359 | { | ||
1360 | ✗ | fpi_ssm_mark_failed (self->task_ssm, | |
1361 | fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, | ||
1362 | "Device firmware response " | ||
1363 | "was not valid.")); | ||
1364 | ✗ | return; | |
1365 | } | ||
1366 | |||
1367 | /* | ||
1368 | * FW Version is 12 bytes: a carriage return (0x0d) plus the version string | ||
1369 | * itself. Always skip [the read prefix] + [2 * check bytes] + [3 * 0x00] that | ||
1370 | * come with every payload Then we will also skip the carriage return and take | ||
1371 | * all but the last 2 bytes as the FW Version | ||
1372 | */ | ||
1373 | 4 | prefix_length = egismoc_read_prefix_len + 2 + 3 + 1; | |
1374 | 4 | fw_version_start = buffer_in + prefix_length; | |
1375 | 4 | fw_version_length = length_in - prefix_length - rsp_fw_version_suffix_len; | |
1376 | 4 | fw_version = g_strndup ((gchar *) fw_version_start, fw_version_length); | |
1377 | |||
1378 | 4 | fp_info ("Device firmware version is %s", fw_version); | |
1379 | |||
1380 | 4 | fpi_ssm_next_state (self->task_ssm); | |
1381 | } | ||
1382 | |||
1383 | static void | ||
1384 | 4 | egismoc_dev_init_done (FpiSsm *ssm, | |
1385 | FpDevice *device, | ||
1386 | GError *error) | ||
1387 | { | ||
1388 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (error) |
1389 | { | ||
1390 | ✗ | g_usb_device_release_interface ( | |
1391 | fpi_device_get_usb_device (device), 0, 0, NULL); | ||
1392 | ✗ | egismoc_task_ssm_done (ssm, device, error); | |
1393 | ✗ | return; | |
1394 | } | ||
1395 | |||
1396 | 4 | egismoc_task_ssm_done (ssm, device, NULL); | |
1397 | 4 | fpi_device_open_complete (device, NULL); | |
1398 | } | ||
1399 | |||
1400 | static void | ||
1401 | 24 | egismoc_dev_init_handler (FpiSsm *ssm, | |
1402 | FpDevice *device) | ||
1403 | { | ||
1404 | 44 | g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device); | |
1405 | |||
1406 |
6/7✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
|
24 | switch (fpi_ssm_get_cur_state (ssm)) |
1407 | { | ||
1408 | 4 | case DEV_INIT_CONTROL1: | |
1409 | 4 | fpi_usb_transfer_fill_control (transfer, | |
1410 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, | ||
1411 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, | ||
1412 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
1413 | 32, 0x0000, 4, 16); | ||
1414 | 4 | break; | |
1415 | |||
1416 | 4 | case DEV_INIT_CONTROL2: | |
1417 | 4 | fpi_usb_transfer_fill_control (transfer, | |
1418 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, | ||
1419 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, | ||
1420 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
1421 | 32, 0x0000, 4, 40); | ||
1422 | 4 | break; | |
1423 | |||
1424 | 4 | case DEV_INIT_CONTROL3: | |
1425 | 4 | fpi_usb_transfer_fill_control (transfer, | |
1426 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, | ||
1427 | G_USB_DEVICE_REQUEST_TYPE_STANDARD, | ||
1428 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
1429 | 0, 0x0000, 0, 2); | ||
1430 | 4 | break; | |
1431 | |||
1432 | 4 | case DEV_INIT_CONTROL4: | |
1433 | 4 | fpi_usb_transfer_fill_control (transfer, | |
1434 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, | ||
1435 | G_USB_DEVICE_REQUEST_TYPE_STANDARD, | ||
1436 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
1437 | 0, 0x0000, 0, 2); | ||
1438 | 4 | break; | |
1439 | |||
1440 | 4 | case DEV_INIT_CONTROL5: | |
1441 | 4 | fpi_usb_transfer_fill_control (transfer, | |
1442 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, | ||
1443 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, | ||
1444 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
1445 | 82, 0x0000, 0, 8); | ||
1446 | 4 | break; | |
1447 | |||
1448 | 4 | case DEV_GET_FW_VERSION: | |
1449 | 4 | egismoc_exec_cmd (device, cmd_fw_version, cmd_fw_version_len, | |
1450 | NULL, egismoc_fw_version_cb); | ||
1451 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | return; |
1452 | |||
1453 | ✗ | default: | |
1454 | ✗ | g_assert_not_reached (); | |
1455 | } | ||
1456 | |||
1457 | 20 | transfer->ssm = ssm; | |
1458 | 20 | transfer->short_is_error = TRUE; | |
1459 | 20 | fpi_usb_transfer_submit (g_steal_pointer (&transfer), | |
1460 | EGISMOC_USB_CONTROL_TIMEOUT, | ||
1461 | fpi_device_get_cancellable (device), | ||
1462 | fpi_ssm_usb_transfer_cb, | ||
1463 | NULL); | ||
1464 | } | ||
1465 | |||
1466 | static void | ||
1467 | 4 | egismoc_probe (FpDevice *device) | |
1468 | { | ||
1469 | 4 | GUsbDevice *usb_dev; | |
1470 | 4 | GError *error = NULL; | |
1471 | 4 | g_autofree gchar *serial = NULL; | |
1472 | 4 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1473 | |||
1474 | 4 | fp_dbg ("%s enter --> ", G_STRFUNC); | |
1475 | |||
1476 | /* Claim usb interface */ | ||
1477 | 4 | usb_dev = fpi_device_get_usb_device (device); | |
1478 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (!g_usb_device_open (usb_dev, &error)) |
1479 | { | ||
1480 | ✗ | fp_dbg ("%s g_usb_device_open failed %s", G_STRFUNC, error->message); | |
1481 | ✗ | fpi_device_probe_complete (device, NULL, NULL, error); | |
1482 | ✗ | return; | |
1483 | } | ||
1484 | |||
1485 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (!g_usb_device_reset (usb_dev, &error)) |
1486 | { | ||
1487 | ✗ | fp_dbg ("%s g_usb_device_reset failed %s", G_STRFUNC, error->message); | |
1488 | ✗ | g_usb_device_close (usb_dev, NULL); | |
1489 | ✗ | fpi_device_probe_complete (device, NULL, NULL, error); | |
1490 | ✗ | return; | |
1491 | } | ||
1492 | |||
1493 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (!g_usb_device_claim_interface (usb_dev, 0, 0, &error)) |
1494 | { | ||
1495 | ✗ | fp_dbg ("%s g_usb_device_claim_interface failed %s", G_STRFUNC, error->message); | |
1496 | ✗ | g_usb_device_close (usb_dev, NULL); | |
1497 | ✗ | fpi_device_probe_complete (device, NULL, NULL, error); | |
1498 | ✗ | return; | |
1499 | } | ||
1500 | |||
1501 |
1/2✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) |
1502 | 4 | serial = g_strdup ("emulated-device"); | |
1503 | else | ||
1504 | ✗ | serial = g_usb_device_get_string_descriptor (usb_dev, | |
1505 | ✗ | g_usb_device_get_serial_number_index (usb_dev), | |
1506 | &error); | ||
1507 | |||
1508 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (error) |
1509 | { | ||
1510 | ✗ | fp_dbg ("%s g_usb_device_get_string_descriptor failed %s", G_STRFUNC, error->message); | |
1511 | ✗ | g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)), | |
1512 | 0, 0, NULL); | ||
1513 | ✗ | g_usb_device_close (usb_dev, NULL); | |
1514 | ✗ | fpi_device_probe_complete (device, NULL, NULL, error); | |
1515 | ✗ | return; | |
1516 | } | ||
1517 | |||
1518 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
|
4 | if (fpi_device_get_driver_data (device) & EGISMOC_DRIVER_MAX_ENROLL_STAGES_20) |
1519 | 2 | self->max_enroll_stages = 20; | |
1520 | else | ||
1521 | 2 | self->max_enroll_stages = EGISMOC_MAX_ENROLL_STAGES_DEFAULT; | |
1522 | |||
1523 | 4 | fpi_device_set_nr_enroll_stages (device, self->max_enroll_stages); | |
1524 | |||
1525 | 4 | g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)), 0, 0, NULL); | |
1526 | 4 | g_usb_device_close (usb_dev, NULL); | |
1527 | |||
1528 | 4 | fpi_device_probe_complete (device, serial, NULL, error); | |
1529 | } | ||
1530 | |||
1531 | static void | ||
1532 | 4 | egismoc_open (FpDevice *device) | |
1533 | { | ||
1534 | 4 | fp_dbg ("Opening device"); | |
1535 | 4 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1536 | 4 | GError *error = NULL; | |
1537 | |||
1538 | 4 | self->interrupt_cancellable = g_cancellable_new (); | |
1539 | |||
1540 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) |
1541 | { | ||
1542 | ✗ | fpi_device_open_complete (device, error); | |
1543 | ✗ | return; | |
1544 | } | ||
1545 | |||
1546 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), |
1547 | 0, 0, &error)) | ||
1548 | { | ||
1549 | ✗ | fpi_device_open_complete (device, error); | |
1550 | ✗ | return; | |
1551 | } | ||
1552 | |||
1553 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | g_assert (self->task_ssm == NULL); |
1554 | 4 | self->task_ssm = fpi_ssm_new (device, egismoc_dev_init_handler, DEV_INIT_STATES); | |
1555 | 4 | fpi_ssm_start (self->task_ssm, egismoc_dev_init_done); | |
1556 | } | ||
1557 | |||
1558 | static void | ||
1559 | 4 | egismoc_cancel (FpDevice *device) | |
1560 | { | ||
1561 | 4 | fp_dbg ("Cancel"); | |
1562 | 4 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1563 | |||
1564 | 4 | g_cancellable_cancel (self->interrupt_cancellable); | |
1565 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | g_clear_object (&self->interrupt_cancellable); |
1566 | 4 | self->interrupt_cancellable = g_cancellable_new (); | |
1567 | 4 | } | |
1568 | |||
1569 | static void | ||
1570 | ✗ | egismoc_suspend (FpDevice *device) | |
1571 | { | ||
1572 | ✗ | fp_dbg ("Suspend"); | |
1573 | |||
1574 | ✗ | egismoc_cancel (device); | |
1575 | ✗ | g_cancellable_cancel (fpi_device_get_cancellable (device)); | |
1576 | ✗ | fpi_device_suspend_complete (device, NULL); | |
1577 | ✗ | } | |
1578 | |||
1579 | static void | ||
1580 | 4 | egismoc_close (FpDevice *device) | |
1581 | { | ||
1582 | 4 | fp_dbg ("Closing device"); | |
1583 | 4 | FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); | |
1584 | 4 | GError *error = NULL; | |
1585 | |||
1586 | 4 | egismoc_cancel (device); | |
1587 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | g_clear_object (&self->interrupt_cancellable); |
1588 | |||
1589 | 4 | g_usb_device_release_interface (fpi_device_get_usb_device (device), | |
1590 | 0, 0, &error); | ||
1591 | 4 | fpi_device_close_complete (device, error); | |
1592 | 4 | } | |
1593 | |||
1594 | static void | ||
1595 | 4 | fpi_device_egismoc_init (FpiDeviceEgisMoc *self) | |
1596 | { | ||
1597 | 4 | G_DEBUG_HERE (); | |
1598 | 4 | } | |
1599 | |||
1600 | static void | ||
1601 | 120 | fpi_device_egismoc_class_init (FpiDeviceEgisMocClass *klass) | |
1602 | { | ||
1603 | 120 | FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); | |
1604 | |||
1605 | 120 | dev_class->id = FP_COMPONENT; | |
1606 | 120 | dev_class->full_name = EGISMOC_DRIVER_FULLNAME; | |
1607 | |||
1608 | 120 | dev_class->type = FP_DEVICE_TYPE_USB; | |
1609 | 120 | dev_class->scan_type = FP_SCAN_TYPE_PRESS; | |
1610 | 120 | dev_class->id_table = egismoc_id_table; | |
1611 | 120 | dev_class->nr_enroll_stages = EGISMOC_MAX_ENROLL_STAGES_DEFAULT; | |
1612 | /* device should be "always off" unless being used */ | ||
1613 | 120 | dev_class->temp_hot_seconds = 0; | |
1614 | |||
1615 | 120 | dev_class->probe = egismoc_probe; | |
1616 | 120 | dev_class->open = egismoc_open; | |
1617 | 120 | dev_class->cancel = egismoc_cancel; | |
1618 | 120 | dev_class->suspend = egismoc_suspend; | |
1619 | 120 | dev_class->close = egismoc_close; | |
1620 | 120 | dev_class->identify = egismoc_identify_verify; | |
1621 | 120 | dev_class->verify = egismoc_identify_verify; | |
1622 | 120 | dev_class->enroll = egismoc_enroll; | |
1623 | 120 | dev_class->delete = egismoc_delete; | |
1624 | 120 | dev_class->clear_storage = egismoc_clear_storage; | |
1625 | 120 | dev_class->list = egismoc_list; | |
1626 | |||
1627 | 120 | fpi_device_class_auto_initialize_features (dev_class); | |
1628 | 120 | dev_class->features |= FP_DEVICE_FEATURE_DUPLICATES_CHECK; | |
1629 | 120 | } | |
1630 |