Compare commits

..

7 Commits

Author SHA1 Message Date
Bastien Nocera
8d871751f6 WIP: shut-up uncrustify 2020-05-07 16:46:00 +02:00
Bastien Nocera
4fdf0eae0f virtual-device: Add non-imaging mock driver
Has 2 variants, one that supports identify, one that doesn't.

The mode of operation for this device is:
- Run fprintd with "FP_VIRTUAL_DEVICE=/tmp/fprint.socket"
- As root:
FP_VIRTUAL_DEVICE=/tmp/fprint.socket ./sendvirtcmd.py "ADD left-middle testuser success"
FP_VIRTUAL_DEVICE=/tmp/fprint.socket ./sendvirtcmd.py "ADD right-middle testuser failure"
- As testuser:
fprintd-enroll -f left-middle-finger
fprintd-enroll -f right-middle-finger

Now, across fprintd restarts (as it has its own storage), verification
will always succeed for user "testuser" with the "left-middle-finger".

Using the FP_VIRTUAL_DEVICE_IDENT envvar instead will allow
identification, with the first found "successful" finger being presented
as the identified one.
2020-05-07 16:46:00 +02:00
Bastien Nocera
8632877b95 examples: Add example to feed non-imaging virtual driver
Add an example script that can add and remove prints from the
non-imaging virtual driver.
2020-05-07 16:46:00 +02:00
Bastien Nocera
170d7c2cf7 virtual-image: Exit early when new connection fails 2020-05-07 16:46:00 +02:00
Bastien Nocera
e2d4b0d249 tests: Add support for creating other virtual readers
Add support for creating more virtual readers.
2020-05-07 16:46:00 +02:00
Bastien Nocera
b0c546164e fpi-device: Allow driver to handle identify for certain variants
Rather than requiring a driver to implement a variant with
identify support, and another one without, ask the driver whether a
particular device supports identification.
2020-05-07 15:01:18 +02:00
Bastien Nocera
f2ae3fb8c5 virtual-image: Remove unnecessary cast 2020-05-07 15:01:18 +02:00
56 changed files with 726 additions and 1195 deletions

View File

@@ -48,11 +48,6 @@ build:
<<: *build_one_driver
<<: *build
# <<: *check_abi
artifacts:
expose_as: "HTML Documentation"
paths:
- _build/doc/html/
expire_in: 1 week
test:
stage: test
@@ -69,7 +64,6 @@ test:
paths:
- _build/meson-logs
expire_in: 1 week
coverage: '/^TOTAL.*\s+(\d+\%)$/'
test_valgrind:
stage: test
@@ -81,32 +75,13 @@ test_valgrind:
- ninja -C _build
- meson test -C _build --verbose --no-stdsplit --setup=valgrind
test_scan_build:
stage: test
except:
variables:
- $CI_PIPELINE_SOURCE == "schedule"
allow_failure: true
script:
- meson -Ddrivers=all . _build
# This is ugly, the wrapper disables the malloc checker
- SCANBUILD=$CI_PROJECT_DIR/.gitlab-ci/scan-build ninja -C _build scan-build
# Check that the directory is empty
- "! ls -A _build/meson-logs/scanbuild | grep -q ."
artifacts:
paths:
- _build/meson-logs
expire_in: 1 week
test_indent:
stage: check-source
except:
variables:
- $CI_PIPELINE_SOURCE == "schedule"
script:
- scripts/uncrustify.sh
- git diff
- "! git status -s | grep -q ."
- scripts/uncrustify.sh --check
.flatpak_script_template: &flatpak_script
script:

View File

@@ -24,4 +24,3 @@
umockdev
uncrustify
valgrind
clang-analyzer

View File

@@ -1,4 +0,0 @@
#!/bin/sh
# This wrapper just disables the malloc checker
exec /usr/bin/scan-build -disable-checker unix.Malloc "$@"

View File

@@ -1,13 +0,0 @@
Current maintainers of libfprint are:
* Benjamin Berg <bberg@redhat.com>
* Marco Trevisan (Treviño) <mail@3v1n0.net>
Many drivers are not actively maintained and may not be fully functional.
We are happy to receive contributions, but the support we can give is
limitted unfortunately. For many drivers we may not even have test devices.
Maintained drivers are:
* synaptics:
Contributed and maintained by Synaptics Inc.
Contact: Vincent Huang <vincent.huang@tw.synaptics.com>

10
NEWS
View File

@@ -1,16 +1,6 @@
This file lists notable changes in each release. For the full history of all
changes, see ChangeLog.
2020-06-08: v1.90.2 release
This release contains a large amount of bug and regression fixes. These
are not listed explicitly, but affect the majority of drivers.
Highlights:
* A patch for nbis required for some sensors was accidentally dropped in
an earlier release. Users of these sensors/drivers (aes1610, aes2501,
aes2550, aes1660, aes2660, elan, upektc_img) need to re-enroll (#142).
2019-11-20: v1.90.1 release
This release fixes a lot of the regressions introduced in 1.90.0. Please note

View File

@@ -175,7 +175,7 @@ fpi_image_resize
<SECTION>
<FILE>fpi-image-device</FILE>
<TITLE>Internal FpImageDevice</TITLE>
<TITLE>FpImageDevice</TITLE>
FpiImageDeviceState
FpImageDeviceClass
fpi_image_device_session_error

53
examples/sendvirtcmd.py Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
# This script can be used together with the virtual_imgdev to simulate an
# image based fingerprint reader.
#
# To use, set the FP_VIRTUAL_IMAGE environment variable for both the
# libfprint using program (e.g. fprintd) and this script.
#
# Usually this would work by adding it into the systemd unit file. The
# best way of doing so is to create
# /etc/systemd/system/fprintd.service.d/fprintd-test.conf
#
# [Service]
# RuntimeDirectory=fprint
# Environment=FP_VIRTUAL_DEVICE=/run/fprint/virtdev_sock
# Environment=G_MESSAGES_DEBUG=all
# ReadWritePaths=$RUNTIME_DIR
#
# After that run:
#
# systemctl daemon-reload
# systemctl restart fprintd.service
#
# You may also need to disable selinux.
#
# Then run this script with e.g.
# FP_VIRTUAL_DEVICE=/run/fprint/virtdev_sock ./sendvirtimg.py "ADD <username> <finger> <success|failure>"
import cairo
import sys
import os
import socket
import struct
if len(sys.argv) != 2:
sys.stderr.write('You need to pass commands!\n')
sys.stderr.write('Usage: ./sendvirtimg.py "ADD <finger> <username> <success|failure>"\n')
sys.exit(1)
command = sys.argv[1]
# Send image through socket
sockaddr = os.environ['FP_VIRTUAL_DEVICE']
if not sockaddr:
sockaddr = os.environ['FP_VIRTUAL_DEVICE_IDENT']
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(sockaddr)
sock.sendall(command.encode('utf-8'))

View File

@@ -614,7 +614,6 @@ capture_read_strip_cb (FpiUsbTransfer *transfer, FpDevice *device,
self->strips = g_slist_reverse (self->strips);
fpi_do_movement_estimation (&assembling_ctx, self->strips);
img = fpi_assemble_frames (&assembling_ctx, self->strips);
img->flags |= FPI_IMAGE_PARTIAL;
g_slist_free_full (self->strips, g_free);
self->strips = NULL;

View File

@@ -458,7 +458,6 @@ capture_read_strip_cb (FpiUsbTransfer *transfer, FpDevice *_dev,
fpi_do_movement_estimation (&assembling_ctx, self->strips);
img = fpi_assemble_frames (&assembling_ctx,
self->strips);
img->flags |= FPI_IMAGE_PARTIAL;
g_slist_free_full (self->strips, g_free);
self->strips = NULL;
self->strips_len = 0;

View File

@@ -230,7 +230,6 @@ capture_set_idle_reqs_cb (FpiUsbTransfer *transfer,
self->strips = g_slist_reverse (self->strips);
img = fpi_assemble_frames (&assembling_ctx, self->strips);
img->flags |= FPI_IMAGE_PARTIAL;
g_slist_free_full (self->strips, g_free);
self->strips = NULL;
self->strips_len = 0;

View File

@@ -331,7 +331,6 @@ capture_set_idle_cmd_cb (FpiUsbTransfer *transfer, FpDevice *device,
priv->strips = g_slist_reverse (priv->strips);
img = fpi_assemble_frames (cls->assembling_ctx, priv->strips);
img->flags |= FPI_IMAGE_PARTIAL;
g_slist_foreach (priv->strips, (GFunc) g_free, NULL);
g_slist_free (priv->strips);
priv->strips = NULL;

View File

@@ -207,7 +207,6 @@ elan_save_img_frame (FpiDeviceElan *elandev)
unsigned int frame_size = elandev->frame_width * elandev->frame_height;
unsigned short *frame = g_malloc (frame_size * sizeof (short));
elan_save_frame (elandev, frame);
unsigned int sum = 0;
@@ -245,7 +244,6 @@ elan_process_frame_linear (unsigned short *raw_frame,
G_DEBUG_HERE ();
unsigned short min = 0xffff, max = 0;
for (int i = 0; i < frame_size; i++)
{
if (raw_frame[i] < min)
@@ -257,7 +255,6 @@ elan_process_frame_linear (unsigned short *raw_frame,
g_assert (max != min);
unsigned short px;
for (int i = 0; i < frame_size; i++)
{
px = raw_frame[i];
@@ -281,7 +278,6 @@ elan_process_frame_thirds (unsigned short *raw_frame,
unsigned short lvl0, lvl1, lvl2, lvl3;
unsigned short *sorted = g_malloc (frame_size * sizeof (short));
memcpy (sorted, raw_frame, frame_size * sizeof (short));
qsort (sorted, frame_size, sizeof (short), cmp_short);
lvl0 = sorted[0];
@@ -291,7 +287,6 @@ elan_process_frame_thirds (unsigned short *raw_frame,
g_free (sorted);
unsigned short px;
for (int i = 0; i < frame_size; i++)
{
px = raw_frame[i];
@@ -325,7 +320,6 @@ elan_submit_image (FpImageDevice *dev)
g_slist_foreach (raw_frames, (GFunc) self->process_frame, &frames);
fpi_do_movement_estimation (&assembling_ctx, frames);
img = fpi_assemble_frames (&assembling_ctx, frames);
img->flags |= FPI_IMAGE_PARTIAL;
g_slist_free_full (frames, g_free);
@@ -515,7 +509,6 @@ elan_stop_capture (FpDevice *dev)
FpiSsm *ssm =
fpi_ssm_new (dev, stop_capture_run_state, STOP_CAPTURE_NUM_STATES);
fpi_ssm_start (ssm, stop_capture_complete);
}
@@ -626,7 +619,6 @@ elan_capture (FpDevice *dev)
elan_dev_reset_state (self);
FpiSsm *ssm =
fpi_ssm_new (dev, capture_run_state, CAPTURE_NUM_STATES);
fpi_ssm_start (ssm, capture_complete);
}
@@ -805,7 +797,6 @@ elan_calibrate (FpDevice *dev)
FpiSsm *ssm = fpi_ssm_new (FP_DEVICE (dev), calibrate_run_state,
CALIBRATE_NUM_STATES);
fpi_ssm_start (ssm, calibrate_complete);
}
@@ -901,7 +892,6 @@ elan_activate (FpImageDevice *dev)
FpiSsm *ssm =
fpi_ssm_new (FP_DEVICE (dev), activate_run_state,
ACTIVATE_NUM_STATES);
fpi_ssm_start (ssm, activate_complete);
}
@@ -998,7 +988,7 @@ static void
elan_change_state_async (FpDevice *dev,
void *data)
{
fp_dbg ("state change dev: %p", dev);
g_message ("state change dev: %p", dev);
elan_change_state (FP_IMAGE_DEVICE (dev));
}
@@ -1015,10 +1005,7 @@ dev_change_state (FpImageDevice *dev, FpiImageDeviceState state)
state = FPI_IMAGE_DEVICE_STATE_INACTIVE;
if (self->dev_state_next == state)
{
fp_dbg ("change to state %d already queued", state);
return;
}
switch (state)
{

View File

@@ -206,7 +206,6 @@ parse_get_enrolled_users_report (bmkt_msg_resp_t *msg_resp, bmkt_response_t *res
get_enroll_templates_resp->query_sequence = extract8 (msg_resp->payload, &offset);
int n = 0;
for (n = 0; n < BMKT_MAX_NUM_TEMPLATES_INTERNAL_FLASH; n++)
{
if (offset >= msg_resp->payload_len)

View File

@@ -47,11 +47,17 @@ enum {
enum sonly_kill_transfers_action {
NOT_KILLING = 0,
/* abort a SSM with an error code */
ABORT_SSM,
/* report an image session error */
IMG_SESSION_ERROR,
/* iterate a SSM to the next state */
ITERATE_SSM,
/* call a callback */
EXEC_CALLBACK,
};
enum sonly_fs {
@@ -91,7 +97,11 @@ struct _FpiDeviceUpeksonly
enum sonly_kill_transfers_action killing_transfers;
GError *kill_error;
union
{
FpiSsm *kill_ssm;
void (*kill_cb)(FpImageDevice *dev);
};
struct fpi_line_asmbl_ctx assembling_ctx;
};
@@ -166,6 +176,11 @@ last_transfer_killed (FpImageDevice *dev)
switch (self->killing_transfers)
{
case ABORT_SSM:
fp_dbg ("abort ssm error %s", self->kill_error->message);
fpi_ssm_mark_failed (self->kill_ssm, g_steal_pointer (&self->kill_error));
return;
case ITERATE_SSM:
fp_dbg ("iterate ssm");
fpi_ssm_next_state (self->kill_ssm);
@@ -516,14 +531,6 @@ img_data_cb (FpiUsbTransfer *transfer, FpDevice *device,
return;
}
/* NOTE: The old code assume 4096 bytes are received each time
* but there is no reason we need to enforce that. However, we
* always need full lines. */
if (transfer->actual_length % 64 != 0)
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Data packets need to be multiple of 64 bytes, got %zi bytes",
transfer->actual_length);
if (error)
{
fp_warn ("bad status %s, terminating session", error->message);
@@ -544,7 +551,7 @@ img_data_cb (FpiUsbTransfer *transfer, FpDevice *device,
* the first 2 bytes are a sequence number
* then there are 62 bytes for image data
*/
for (i = 0; i + 64 <= transfer->actual_length; i += 64)
for (i = 0; i < 4096; i += 64)
{
if (!is_capturing (self))
return;
@@ -553,7 +560,7 @@ img_data_cb (FpiUsbTransfer *transfer, FpDevice *device,
if (is_capturing (self))
{
fpi_usb_transfer_submit (fpi_usb_transfer_ref (transfer),
fpi_usb_transfer_submit (transfer,
0,
self->img_cancellable,
img_data_cb,
@@ -581,8 +588,6 @@ write_regs_finished (struct write_regs_data *wrdata, GError *error)
fpi_ssm_next_state (wrdata->ssm);
else
fpi_ssm_mark_failed (wrdata->ssm, error);
g_free (wrdata);
}
static void write_regs_iterate (struct write_regs_data *wrdata);
@@ -629,9 +634,9 @@ write_regs_iterate (struct write_regs_data *wrdata)
1);
transfer->short_is_error = TRUE;
transfer->ssm = wrdata->ssm;
transfer->buffer[0] = regwrite->value;
fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, write_regs_cb, NULL);
fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, write_regs_cb, wrdata);
transfer->buffer[0] = regwrite->value;
}
static void
@@ -670,10 +675,10 @@ sm_write_reg (FpiSsm *ssm,
1);
transfer->short_is_error = TRUE;
transfer->ssm = ssm;
transfer->buffer[0] = value;
fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
transfer->buffer[0] = value;
}
static void
@@ -903,7 +908,7 @@ capsm_fire_bulk (FpiSsm *ssm,
self->img_cancellable = g_cancellable_new ();
for (i = 0; i < self->img_transfers->len; i++)
{
fpi_usb_transfer_submit (fpi_usb_transfer_ref (g_ptr_array_index (self->img_transfers, i)),
fpi_usb_transfer_submit (g_ptr_array_index (self->img_transfers, i),
0,
self->img_cancellable,
img_data_cb,
@@ -1401,12 +1406,8 @@ dev_activate (FpImageDevice *dev)
self->capturing = FALSE;
self->num_flying = 0;
self->img_transfers = g_ptr_array_new_with_free_func ((GFreeFunc) fpi_usb_transfer_unref);
/* This might seem odd, but we do need multiple in-flight URBs so that
* we never stop polling the device for more data.
*/
for (i = 0; i < NUM_BULK_TRANSFERS; i++)
for (i = 0; i < self->img_transfers->len; i++)
{
FpiUsbTransfer *transfer;

View File

@@ -310,7 +310,6 @@ capture_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device,
fp_dbg ("Image size is %lu\n",
self->image_size);
img = fp_image_new (IMAGE_WIDTH, IMAGE_HEIGHT);
img->flags |= FPI_IMAGE_PARTIAL;
memcpy (img->data, self->image_bits,
IMAGE_SIZE);
fpi_image_device_image_captured (dev, img);

View File

@@ -35,6 +35,7 @@
#define TIMEOUT 5000
#define MSG_READ_BUF_SIZE 0x40
#define MAX_DATA_IN_READ_BUF (MSG_READ_BUF_SIZE - 9)
struct _FpiDeviceUpekts
{
@@ -235,18 +236,12 @@ __handle_incoming_msg (FpDevice *device,
{
GError *error = NULL;
guint8 *buf = udata->buffer;
guint16 len;
guint16 computed_crc;
guint16 msg_crc;
guint16 len = ((buf[5] & 0xf) << 8) | buf[6];
guint16 computed_crc = udf_crc (buf + 4, len + 3);
guint16 msg_crc = (buf[len + 8] << 8) | buf[len + 7];
unsigned char *retdata = NULL;
unsigned char code_a, code_b;
g_assert (udata->buflen >= 6);
len = ((buf[5] & 0xf) << 8) | buf[6];
g_assert (udata->buflen >= len + 9);
computed_crc = udf_crc (buf + 4, len + 3);
msg_crc = (buf[len + 8] << 8) | buf[len + 7];
if (computed_crc != msg_crc)
{
fp_err ("CRC failed, got %04x expected %04x", msg_crc, computed_crc);
@@ -272,7 +267,12 @@ __handle_incoming_msg (FpDevice *device,
return;
}
udata->callback (device, READ_MSG_CMD, code_a, 0, buf + 7, len,
if (len > 0)
{
retdata = g_malloc (len);
memcpy (retdata, buf + 7, len);
}
udata->callback (device, READ_MSG_CMD, code_a, 0, retdata, len,
udata->user_data, NULL);
goto done;
}
@@ -309,8 +309,14 @@ __handle_incoming_msg (FpDevice *device,
innerlen = innerlen - 3;
_subcmd = innerbuf[5];
fp_dbg ("device responds to subcmd %x with %d bytes", _subcmd, innerlen);
if (innerlen > 0)
{
retdata = g_malloc (innerlen);
memcpy (retdata, innerbuf + 6, innerlen);
}
udata->callback (device, READ_MSG_RESPONSE, code_b, _subcmd,
innerbuf + 6, innerlen, udata->user_data, NULL);
retdata, innerlen, udata->user_data, NULL);
g_free (retdata);
goto done;
}
else
@@ -352,8 +358,7 @@ read_msg_cb (FpiUsbTransfer *transfer, FpDevice *device,
gpointer user_data, GError *error)
{
struct read_msg_data *udata = user_data;
guint16 payload_len;
gsize packet_len;
guint16 len;
if (error)
{
@@ -378,15 +383,14 @@ read_msg_cb (FpiUsbTransfer *transfer, FpDevice *device,
goto err;
}
payload_len = ((udata->buffer[5] & 0xf) << 8) | udata->buffer[6];
packet_len = payload_len + 9;
len = ((udata->buffer[5] & 0xf) << 8) | udata->buffer[6];
if (transfer->actual_length != MSG_READ_BUF_SIZE &&
packet_len > transfer->actual_length)
(len + 9) > transfer->actual_length)
{
/* Check that the length claimed inside the message is in line with
* the amount of data that was transferred over USB. */
fp_err ("msg didn't include enough data, expected=%d recv=%d",
(gint) packet_len, (gint) transfer->actual_length);
len + 9, (gint) transfer->actual_length);
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Packet from device didn't include data");
goto err;
@@ -395,14 +399,14 @@ read_msg_cb (FpiUsbTransfer *transfer, FpDevice *device,
/* We use a 64 byte buffer for reading messages. However, sometimes
* messages are longer, in which case we have to do another USB bulk read
* to read the remainder. This is handled below. */
if (packet_len > MSG_READ_BUF_SIZE)
if (len > MAX_DATA_IN_READ_BUF)
{
int needed = packet_len - MSG_READ_BUF_SIZE;
int needed = len - MAX_DATA_IN_READ_BUF;
FpiUsbTransfer *etransfer = fpi_usb_transfer_new (device);
fp_dbg ("didn't fit in buffer, need to extend by %d bytes", needed);
udata->buffer = g_realloc ((gpointer) udata->buffer, packet_len);
udata->buflen = packet_len;
udata->buffer = g_realloc ((gpointer) udata->buffer, len);
udata->buflen = len;
fpi_usb_transfer_fill_bulk_full (etransfer, EP_IN,
udata->buffer + MSG_READ_BUF_SIZE,
@@ -696,7 +700,7 @@ initsm_run_state (FpiSsm *ssm, FpDevice *dev)
break;
case SEND_RESP03:;
transfer = alloc_send_cmdresponse_transfer (dev, ++upekdev->seq, init_resp03, sizeof (init_resp03));
transfer = alloc_send_cmd28_transfer (dev, ++upekdev->seq, init_resp03, sizeof (init_resp03));
transfer->ssm = ssm;
transfer->short_is_error = TRUE;
fpi_usb_transfer_submit (transfer, TIMEOUT, NULL, fpi_ssm_usb_transfer_cb, NULL);
@@ -853,15 +857,22 @@ dev_init (FpDevice *dev)
}
static void
dev_exit (FpDevice *dev)
deinitsm_done (FpiSsm *ssm, FpDevice *dev, GError *error)
{
GError *error = NULL;
g_usb_device_release_interface (fpi_device_get_usb_device (dev), 0, 0, &error);
g_usb_device_release_interface (fpi_device_get_usb_device (dev), 0, 0, NULL);
fpi_device_close_complete (dev, error);
}
static void
dev_exit (FpDevice *dev)
{
FpiSsm *ssm;
ssm = fpi_ssm_new (dev, deinitsm_state_handler, DEINITSM_NUM_STATES);
fpi_ssm_start (ssm, deinitsm_done);
}
static const unsigned char enroll_init[] = {
0x02, 0xc0, 0xd4, 0x01, 0x00, 0x04, 0x00, 0x08
};
@@ -972,9 +983,7 @@ enroll_stop_deinit_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
if (error)
fp_warn ("Error deinitializing: %s", error->message);
fpi_device_enroll_complete (dev,
g_steal_pointer (&data->print),
g_steal_pointer (&data->error));
fpi_device_enroll_complete (dev, data->print, data->error);
}
static void
@@ -983,7 +992,7 @@ do_enroll_stop (FpDevice *dev, FpPrint *print, GError *error)
EnrollStopData *data = g_new0 (EnrollStopData, 1);
FpiSsm *ssm = deinitsm_new (dev, data);
data->print = print;
data->print = g_object_ref (print);
data->error = error;
fpi_ssm_start (ssm, enroll_stop_deinit_cb);
@@ -1118,7 +1127,6 @@ e_handle_resp02 (FpDevice *dev, unsigned char *data,
data_len - sizeof (scan_comp),
1);
fpi_print_set_type (print, FPI_PRINT_RAW);
g_object_set (print, "fpi-data", fp_data, NULL);
g_object_ref (print);
}
@@ -1237,11 +1245,11 @@ verify_stop_deinit_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
fp_warn ("Error deinitializing: %s", error->message);
if (data->error)
fpi_device_verify_complete (dev, g_steal_pointer (&data->error));
fpi_device_verify_complete (dev, data->error);
else
fpi_device_verify_complete (dev, g_steal_pointer (&error));
g_clear_error (&error);
g_error_free (error);
}
static void
@@ -1251,7 +1259,7 @@ do_verify_stop (FpDevice *dev, FpiMatchResult res, GError *error)
FpiSsm *ssm = deinitsm_new (dev, data);
/* Report the error immediately if possible, otherwise delay it. */
if (error && error->domain == FP_DEVICE_RETRY)
if (!error && error->domain != FP_DEVICE_RETRY)
fpi_device_verify_report (dev, res, NULL, error);
else
data->error = error;
@@ -1293,7 +1301,7 @@ verify_start_sm_run_state (FpiSsm *ssm, FpDevice *dev)
case VERIFY_INIT:
fpi_device_get_verify_data (dev, &print);
g_object_get (print, "fpi-data", &fp_data, NULL);
g_object_get (dev, "fpi-data", &fp_data, NULL);
data = g_variant_get_fixed_array (fp_data, &data_len, 1);

View File

@@ -81,7 +81,7 @@ static const struct uru4k_dev_profile
{
const char *name;
gboolean auth_cr;
gboolean image_not_flipped;
gboolean encryption;
} uru4k_dev_info[] = {
[MS_KBD] = {
.name = "Microsoft Keyboard with Fingerprint Reader",
@@ -106,7 +106,7 @@ static const struct uru4k_dev_profile
[DP_URU4000B] = {
.name = "Digital Persona U.are.U 4000B",
.auth_cr = FALSE,
.image_not_flipped = TRUE, /* See comment in the code where it is used. */
.encryption = TRUE,
},
};
@@ -680,8 +680,8 @@ imaging_run_state (FpiSsm *ssm, FpDevice *_dev)
fpi_ssm_jump_to_state (ssm, IMAGING_CAPTURE);
return;
}
/* Detect whether image is encrypted (by checking how noisy it is) */
if (!self->profile->encryption)
{
dev2 = calc_dev2 (img);
fp_dbg ("dev2: %d", dev2);
if (dev2 < ENC_THRESHOLD)
@@ -690,7 +690,7 @@ imaging_run_state (FpiSsm *ssm, FpDevice *_dev)
return;
}
fp_info ("image seems to be encrypted");
}
buf[0] = img->key_number;
buf[1] = self->img_enc_seed;
buf[2] = self->img_enc_seed >> 8;
@@ -769,13 +769,7 @@ imaging_run_state (FpiSsm *ssm, FpDevice *_dev)
}
fpimg->flags = FPI_IMAGE_COLORS_INVERTED;
/* NOTE: For some reason all but U4000B (or rather U4500?) flipped the
* image, we retain this behaviour here, but it is not clear whether it
* is correct.
* It may be that there are different models with the same USB ID that
* behave differently.
*/
if (self->profile->image_not_flipped)
if (!self->profile->encryption)
fpimg->flags |= FPI_IMAGE_V_FLIPPED | FPI_IMAGE_H_FLIPPED;
fpi_image_device_image_captured (dev, fpimg);

View File

@@ -117,10 +117,9 @@ async_abort_callback (FpiUsbTransfer *transfer, FpDevice *device,
int ep = transfer->endpoint;
/* In normal case endpoint is empty */
if (g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT) ||
(g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0 && transfer->actual_length == 0))
if (g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT))
{
g_clear_error (&error);
g_error_free (error);
fpi_ssm_next_state (transfer->ssm);
return;
}
@@ -157,8 +156,6 @@ async_abort (FpDevice *dev, FpiSsm *ssm, int ep)
else
fpi_usb_transfer_fill_bulk (transfer, ep, VFS_USB_BUFFER_SIZE);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, VFS_USB_ABORT_TIMEOUT, NULL,
async_abort_callback, NULL);
}
@@ -243,7 +240,6 @@ prepare_image (FpDeviceVfs0050 *vdev)
/* Building GSList */
GSList *lines = NULL;
for (int i = height - 1; i >= 0; --i)
lines = g_slist_prepend (lines, vdev->lines_buffer + i);
@@ -468,8 +464,8 @@ receive_callback (FpiUsbTransfer *transfer, FpDevice *device,
if (error)
g_error_free (error);
/* Capture is done when there is no more data to transfer or device timed out */
if (transfer->actual_length <= 0)
/* Check if fingerprint data is over */
if (transfer->actual_length == 0)
{
fpi_ssm_next_state (transfer->ssm);
}
@@ -477,7 +473,7 @@ receive_callback (FpiUsbTransfer *transfer, FpDevice *device,
{
self->bytes += transfer->actual_length;
/* Try reading more data */
/* We need more data */
fpi_ssm_jump_to_state (transfer->ssm,
fpi_ssm_get_cur_state (transfer->ssm));
}
@@ -599,7 +595,8 @@ activate_ssm (FpiSsm *ssm, FpDevice *dev)
/* Receive chunk of data */
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_bulk_full (transfer, 0x82,
(guint8 *) self->lines_buffer + self->bytes,
(guint8 *)
(self->lines_buffer + self->bytes),
VFS_USB_BUFFER_SIZE, NULL);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, VFS_USB_TIMEOUT, NULL,
@@ -671,7 +668,6 @@ dev_activate (FpImageDevice *idev)
self->ssm_active = 1;
FpiSsm *ssm = fpi_ssm_new (FP_DEVICE (idev), activate_ssm, SSM_STATES);
fpi_ssm_start (ssm, dev_activate_callback);
}
@@ -715,7 +711,6 @@ dev_open (FpImageDevice *idev)
/* Clearing previous device state */
FpiSsm *ssm = fpi_ssm_new (FP_DEVICE (idev), activate_ssm, SSM_STATES);
fpi_ssm_start (ssm, dev_open_callback);
}

View File

@@ -177,7 +177,6 @@ translate_str (const char **srcL, gssize *len)
src_len += tmp;
}
g_assert (src_len >= 2);
*len = src_len / 2;
res = g_malloc0 (*len);
dst = res;
@@ -438,7 +437,7 @@ img_process_data (int first_block, FpDeviceVfs301 *dev, const guint8 *buf, int l
usb_send (dev, data, len, NULL); \
}
#define RAW_DATA(x) g_memdup (x, sizeof (x)), sizeof (x)
#define RAW_DATA(x) x, sizeof (x)
#define IS_VFS301_FP_SEQ_START(b) ((b[0] == 0x01) && (b[1] == 0xfe))

View File

@@ -381,8 +381,9 @@ submit_image (FpiSsm *ssm,
{
FpImage *img;
if (self->lines_recorded < VFS5011_IMAGE_WIDTH)
if (self->lines_recorded == 0)
{
/* == FP_ENROLL_RETRY_TOO_SHORT */
fpi_image_device_retry_scan (dev, FP_DEVICE_RETRY_TOO_SHORT);
return;
}

View File

@@ -0,0 +1,456 @@
/*
* Virtual driver for "simple" device debugging
*
* Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
* Copyright (C) 2020 Bastien Nocera <hadess@hadess.net>
*
* 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.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* This is a virtual driver to debug the non-image based drivers. A small
* python script is provided to connect to it via a socket, allowing
* prints to registered programmatically.
* Using this, it is possible to test libfprint and fprintd.
*/
#define FP_COMPONENT "virtual_device"
#include "fpi-log.h"
#include "../fpi-device.h"
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <gio/gunixsocketaddress.h>
#define MAX_LINE_LEN 1024
enum {
VIRTUAL_DEVICE,
VIRTUAL_DEVICE_IDENT
};
struct _FpDeviceVirtualDevice
{
FpDevice parent;
GSocketListener *listener;
GSocketConnection *connection;
GCancellable *cancellable;
gint socket_fd;
gint client_fd;
guint line[MAX_LINE_LEN];
GHashTable *pending_prints; /* key: finger+username value: gboolean */
};
G_DECLARE_FINAL_TYPE (FpDeviceVirtualDevice, fpi_device_virtual_device, FP, DEVICE_VIRTUAL_DEVICE, FpDevice)
G_DEFINE_TYPE (FpDeviceVirtualDevice, fpi_device_virtual_device, FP_TYPE_DEVICE)
static void start_listen (FpDeviceVirtualDevice *self);
#define ADD_CMD_PREFIX "ADD "
static FpFinger
str_to_finger (const char *str)
{
g_autoptr(GEnumClass) eclass;
GEnumValue *value;
eclass = g_type_class_ref (FP_TYPE_FINGER);
value = g_enum_get_value_by_nick (eclass, str);
if (value == NULL)
return FP_FINGER_UNKNOWN;
return value->value;
}
static const char *
finger_to_str (FpFinger finger)
{
GEnumClass *eclass;
GEnumValue *value;
eclass = g_type_class_ref (FP_TYPE_FINGER);
value = g_enum_get_value (eclass, finger);
g_type_class_unref (eclass);
if (value == NULL)
return NULL;
return value->value_nick;
}
static gboolean
parse_code (const char *str)
{
if (g_strcmp0 (str, "1") == 0 ||
g_strcmp0 (str, "success") == 0 ||
g_strcmp0 (str, "SUCCESS") == 0 ||
g_strcmp0 (str, "FPI_MATCH_SUCCESS") == 0)
return FPI_MATCH_SUCCESS;
return FPI_MATCH_FAIL;
}
static void
handle_command_line (FpDeviceVirtualDevice *self,
const char *line)
{
if (g_str_has_prefix (line, ADD_CMD_PREFIX))
{
g_auto(GStrv) elems;
FpPrint *print;
FpFinger finger;
gboolean success;
g_autofree char *description = NULL;
char *key;
/* Syntax: ADD <finger> <username> <error when used> */
elems = g_strsplit (line + strlen (ADD_CMD_PREFIX), " ", 3);
if (g_strv_length (elems) != 3)
{
g_warning ("Malformed command: %s", line);
return;
}
finger = str_to_finger (elems[0]);
if (finger == FP_FINGER_UNKNOWN)
{
g_warning ("Unknown finger '%s'", elems[0]);
return;
}
print = fp_print_new (FP_DEVICE (self));
fp_print_set_finger (print, finger);
fp_print_set_username (print, elems[1]);
description = g_strdup_printf ("Fingerprint finger '%s' for user '%s'",
elems[0], elems[1]);
fp_print_set_description (print, description);
success = parse_code (elems[2]);
key = g_strdup_printf ("%s-%s", elems[0], elems[1]);
g_hash_table_insert (self->pending_prints,
key, GINT_TO_POINTER (success));
fp_dbg ("Added pending print %s for user %s (code: %s)",
elems[0], elems[1], success ? "FPI_MATCH_SUCCESS" : "FPI_MATCH_FAIL");
}
else
{
g_warning ("Unhandled command sent: '%s'", line);
}
}
static void
recv_instruction_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GError) error = NULL;
FpDeviceVirtualDevice *self;
gboolean success;
gsize bytes;
success = g_input_stream_read_all_finish (G_INPUT_STREAM (source_object), res, &bytes, &error);
if (!success || bytes == 0)
{
if (!success)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED))
return;
g_warning ("Error receiving instruction data: %s", error->message);
}
self = FP_DEVICE_VIRTUAL_DEVICE (user_data);
goto out;
}
self = FP_DEVICE_VIRTUAL_DEVICE (user_data);
handle_command_line (self, (const char *) self->line);
out:
g_io_stream_close (G_IO_STREAM (self->connection), NULL, NULL);
g_clear_object (&self->connection);
start_listen (self);
}
static void
recv_instruction (FpDeviceVirtualDevice *self,
GInputStream *stream)
{
memset (&self->line, 0, sizeof (self->line));
g_input_stream_read_all_async (stream,
self->line,
sizeof (self->line),
G_PRIORITY_DEFAULT,
self->cancellable,
recv_instruction_cb,
self);
}
static void
new_connection_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GError) error = NULL;
g_autoptr(GSocketConnection) connection = NULL;
GInputStream *stream;
FpDeviceVirtualDevice *self = user_data;
connection = g_socket_listener_accept_finish (G_SOCKET_LISTENER (source_object),
res,
NULL,
&error);
if (!connection)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Error accepting a new connection: %s", error->message);
start_listen (self);
return;
}
/* Always further connections (but we disconnect them immediately
* if we already have a connection). */
if (self->connection)
{
g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
start_listen (self);
return;
}
self->connection = g_steal_pointer (&connection);
stream = g_io_stream_get_input_stream (G_IO_STREAM (connection));
recv_instruction (self, stream);
fp_dbg ("Got a new connection!");
}
static void
start_listen (FpDeviceVirtualDevice *self)
{
fp_dbg ("Starting a new listener");
g_socket_listener_accept_async (self->listener,
self->cancellable,
new_connection_cb,
self);
}
static void
dev_init (FpDevice *dev)
{
fpi_device_open_complete (dev, NULL);
}
static void
dev_verify (FpDevice *dev)
{
FpPrint *print;
g_autoptr(GVariant) data = NULL;
gboolean success;
fpi_device_get_verify_data (dev, &print);
g_object_get (print, "fpi-data", &data, NULL);
success = g_variant_get_boolean (data);
fpi_device_verify_report (dev,
success ? FPI_MATCH_SUCCESS : FPI_MATCH_FAIL,
NULL, NULL);
fpi_device_verify_complete (dev, NULL);
}
static void
dev_enroll (FpDevice *dev)
{
FpDeviceVirtualDevice *self = FP_DEVICE_VIRTUAL_DEVICE (dev);
gpointer success_ptr;
FpPrint *print = NULL;
g_autofree char *key = NULL;
fpi_device_get_enroll_data (dev, &print);
key = g_strdup_printf ("%s-%s",
finger_to_str (fp_print_get_finger (print)),
fp_print_get_username (print));
if (g_hash_table_lookup_extended (self->pending_prints, key, NULL, &success_ptr))
{
gboolean success = GPOINTER_TO_INT (success_ptr);
GVariant *fp_data;
fp_data = g_variant_new_boolean (success);
fpi_print_set_type (print, FPI_PRINT_RAW);
if (fpi_device_get_driver_data (dev) == VIRTUAL_DEVICE_IDENT)
fpi_print_set_device_stored (print, TRUE);
g_object_set (print, "fpi-data", fp_data, NULL);
fpi_device_enroll_complete (dev, g_object_ref (print), NULL);
}
else
{
fpi_device_enroll_complete (dev, NULL,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"No pending result for this username/finger combination"));
}
}
static void
dev_deinit (FpDevice *dev)
{
fpi_device_close_complete (dev, NULL);
}
static gboolean
dev_supports_identify (FpDevice *dev)
{
return fpi_device_get_driver_data (dev) == VIRTUAL_DEVICE_IDENT;
}
static void
dev_identify (FpDevice *dev)
{
GPtrArray *templates;
FpPrint *result = NULL;
guint i;
g_assert (fpi_device_get_driver_data (dev) == VIRTUAL_DEVICE_IDENT);
fpi_device_get_identify_data (dev, &templates);
for (i = 0; i < templates->len; i++)
{
FpPrint *template = g_ptr_array_index (templates, i);
g_autoptr(GVariant) data = NULL;
gboolean success;
g_object_get (dev, "fpi-data", &template, NULL);
success = g_variant_get_boolean (data);
if (success)
{
result = template;
break;
}
}
if (result)
fpi_device_identify_report (dev, result, NULL, NULL);
fpi_device_identify_complete (dev, NULL);
}
static void
fpi_device_virtual_device_finalize (GObject *object)
{
FpDeviceVirtualDevice *self = FP_DEVICE_VIRTUAL_DEVICE (object);
G_DEBUG_HERE ();
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
g_clear_object (&self->listener);
g_clear_object (&self->connection);
g_hash_table_destroy (self->pending_prints);
}
static void
fpi_device_virtual_device_constructed (GObject *object)
{
g_autoptr(GError) error = NULL;
g_autoptr(GSocketListener) listener = NULL;
FpDeviceVirtualDevice *self = FP_DEVICE_VIRTUAL_DEVICE (object);
const char *env;
g_autoptr(GSocketAddress) addr = NULL;
G_DEBUG_HERE ();
self->client_fd = -1;
env = fpi_device_get_virtual_env (FP_DEVICE (self));
listener = g_socket_listener_new ();
g_socket_listener_set_backlog (listener, 1);
/* Remove any left over socket. */
g_unlink (env);
addr = g_unix_socket_address_new (env);
if (!g_socket_listener_add_address (listener,
addr,
G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT,
NULL,
NULL,
&error))
{
g_warning ("Could not listen on unix socket: %s", error->message);
fpi_device_open_complete (FP_DEVICE (self), g_steal_pointer (&error));
return;
}
self->listener = g_steal_pointer (&listener);
self->cancellable = g_cancellable_new ();
self->pending_prints = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
NULL);
if (G_OBJECT_CLASS (fpi_device_virtual_device_parent_class)->constructed)
G_OBJECT_CLASS (fpi_device_virtual_device_parent_class)->constructed (object);
start_listen (self);
}
static void
fpi_device_virtual_device_init (FpDeviceVirtualDevice *self)
{
}
static const FpIdEntry driver_ids[] = {
{ .virtual_envvar = "FP_VIRTUAL_DEVICE", .driver_data = VIRTUAL_DEVICE },
{ .virtual_envvar = "FP_VIRTUAL_DEVICE_IDENT", .driver_data = VIRTUAL_DEVICE_IDENT },
{ .virtual_envvar = NULL }
};
static void
fpi_device_virtual_device_class_init (FpDeviceVirtualDeviceClass *klass)
{
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = fpi_device_virtual_device_constructed;
object_class->finalize = fpi_device_virtual_device_finalize;
dev_class->id = FP_COMPONENT;
dev_class->full_name = "Virtual device for debugging";
dev_class->type = FP_DEVICE_TYPE_VIRTUAL;
dev_class->id_table = driver_ids;
dev_class->nr_enroll_stages = 5;
dev_class->open = dev_init;
dev_class->close = dev_deinit;
dev_class->verify = dev_verify;
dev_class->enroll = dev_enroll;
dev_class->identify = dev_identify;
dev_class->supports_identify = dev_supports_identify;
}

View File

@@ -215,6 +215,7 @@ new_connection_cb (GObject *source_object, GAsyncResult *res, gpointer user_data
g_warning ("Error accepting a new connection: %s", error->message);
start_listen (dev);
return;
}
/* Always further connections (but we disconnect them immediately
@@ -252,7 +253,6 @@ dev_init (FpImageDevice *dev)
g_autoptr(GSocketListener) listener = NULL;
FpDeviceVirtualImage *self = FPI_DEVICE_VIRTUAL_IMAGE (dev);
const char *env;
g_autoptr(GSocketAddress) addr = NULL;
G_DEBUG_HERE ();
@@ -278,7 +278,7 @@ dev_init (FpImageDevice *dev)
{
g_warning ("Could not listen on unix socket: %s", error->message);
fpi_image_device_open_complete (FP_IMAGE_DEVICE (dev), g_steal_pointer (&error));
fpi_image_device_open_complete (dev, g_steal_pointer (&error));
return;
}

View File

@@ -24,11 +24,16 @@
#include "fp-device-private.h"
/**
* SECTION: fp-device
* @title: FpDevice
* @short_description: Fingerpint device routines
* SECTION: fpi-device
* @title: Internal FpDevice
* @short_description: Internal device routines
*
* These are the public #FpDevice routines.
* The methods that are available for drivers to manipulate a device. See
* #FpDeviceClass for more information. Also note that most of these are
* not relevant for image based devices, see #FpImageDeviceClass in that
* case.
*
* Also see the public #FpDevice routines.
*/
static void fp_device_async_initable_iface_init (GAsyncInitableIface *iface);
@@ -502,6 +507,9 @@ fp_device_supports_identify (FpDevice *device)
g_return_val_if_fail (FP_IS_DEVICE (device), FALSE);
if (cls->supports_identify != NULL)
return cls->supports_identify (device);
return cls->identify != NULL;
}
@@ -941,6 +949,13 @@ fp_device_identify (FpDevice *device,
if (g_task_return_error_if_cancelled (task))
return;
if (!fp_device_supports_identify (device))
{
g_task_return_error (task,
fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED));
return;
}
if (!priv->is_open)
{
g_task_return_error (task,

View File

@@ -62,34 +62,17 @@ static gboolean
pending_activation_timeout (gpointer user_data)
{
FpImageDevice *self = FP_IMAGE_DEVICE (user_data);
FpDevice *device = FP_DEVICE (self);
FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
FpiDeviceAction action = fpi_device_get_current_action (device);
GError *error;
priv->pending_activation_timeout_id = 0;
if (priv->pending_activation_timeout_waiting_finger_off)
error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER,
"Remove finger before requesting another scan operation");
fpi_device_action_error (FP_DEVICE (self),
fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER,
"Remove finger before requesting another scan operation"));
else
error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL);
if (action == FPI_DEVICE_ACTION_VERIFY)
{
fpi_device_verify_report (device, FPI_MATCH_ERROR, NULL, error);
fpi_device_verify_complete (device, NULL);
}
else if (action == FPI_DEVICE_ACTION_IDENTIFY)
{
fpi_device_identify_report (device, NULL, NULL, error);
fpi_device_identify_complete (device, NULL);
}
else
{
/* Can this happen for enroll? */
fpi_device_action_error (device, error);
}
fpi_device_action_error (FP_DEVICE (self),
fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL));
return G_SOURCE_REMOVE;
}

View File

@@ -281,7 +281,6 @@ fp_image_detect_minutiae_thread_func (GTask *task,
gint map_w, map_h;
gint bw, bh, bd;
gint r;
g_autofree LFSPARMS *lfsparms;
/* Normalize the image first */
if (data->flags & FPI_IMAGE_H_FLIPPED)
@@ -295,15 +294,12 @@ fp_image_detect_minutiae_thread_func (GTask *task,
data->flags &= ~(FPI_IMAGE_H_FLIPPED | FPI_IMAGE_V_FLIPPED | FPI_IMAGE_COLORS_INVERTED);
lfsparms = g_memdup (&g_lfsparms_V2, sizeof (LFSPARMS));
lfsparms->remove_perimeter_pts = data->flags & FPI_IMAGE_PARTIAL ? TRUE : FALSE;
timer = g_timer_new ();
r = get_minutiae (&minutiae, &quality_map, &direction_map,
&low_contrast_map, &low_flow_map, &high_curve_map,
&map_w, &map_h, &bdata, &bw, &bh, &bd,
data->image, data->width, data->height, 8,
data->ppmm, lfsparms);
data->ppmm, &g_lfsparms_V2);
g_timer_stop (timer);
fp_dbg ("Minutiae scan completed in %f secs", g_timer_elapsed (timer, NULL));

View File

@@ -822,7 +822,7 @@ fp_print_deserialize (const guchar *data,
fpi_print_set_type (result, FPI_PRINT_NBIS);
for (i = 0; i < g_variant_n_children (prints); i++)
{
g_autofree struct xyt_struct *xyt = NULL;
g_autofree struct xyt_struct *xyt = g_new0 (struct xyt_struct, 1);
const gint32 *xcol, *ycol, *thetacol;
gsize xlen, ylen, thetalen;
g_autoptr(GVariant) xyt_data = NULL;
@@ -848,7 +848,6 @@ fp_print_deserialize (const guchar *data,
if (xlen > G_N_ELEMENTS (xyt->xcol))
goto invalid_format;
xyt = g_new0 (struct xyt_struct, 1);
xyt->nrows = xlen;
memcpy (xyt->xcol, xcol, sizeof (xcol[0]) * xlen);
memcpy (xyt->ycol, ycol, sizeof (xcol[0]) * xlen);

View File

@@ -912,20 +912,6 @@ fpi_device_enroll_complete (FpDevice *device, FpPrint *print, GError *error)
{
if (FP_IS_PRINT (print))
{
FpiPrintType print_type;
g_object_get (print, "fpi-type", &print_type, NULL);
if (print_type == FPI_PRINT_UNDEFINED)
{
g_warning ("Driver did not set the type on the returned print!");
g_clear_object (&print);
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Driver provided incorrect print data!");
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
return;
}
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_OBJECT, print);
}
else

View File

@@ -79,6 +79,7 @@ struct _FpIdEntry
* @delete: Delete a print from the device
* @cancel: Called on cancellation, this is a convenience to not need to handle
* the #GCancellable directly by using fpi_device_get_cancellable().
* @supports_identify: Whether identify operations are supported.
*
* NOTE: If your driver is image based, then you should subclass #FpImageDevice
* instead. #FpImageDevice based drivers use a different way of interacting
@@ -129,6 +130,8 @@ struct _FpDeviceClass
void (*delete) (FpDevice * device);
void (*cancel) (FpDevice *device);
gboolean (*supports_identify) (FpDevice *device);
};
/**

View File

@@ -24,11 +24,12 @@
#include "fp-image-device.h"
/**
* SECTION: fpi-image-device
* @title: Internal FpImageDevice
* @short_description: Internal image device functions
* SECTION: fpi-image
* @title: Internal FpImage
* @short_description: Internal image handling routines
*
* Internal image device functions. See #FpImageDevice for public routines.
* Internal image handling routines. Also see the public <ulink
* url="libfprint-FpImage.html">FpImage routines</ulink>.
*/
/* Manually redefine what G_DEFINE_* macro does */

View File

@@ -34,7 +34,8 @@
* @title: Internal FpImage
* @short_description: Internal image handling routines
*
* Internal image handling routines. See #FpImage for public routines.
* Internal image handling routines. Also see the public <ulink
* url="libfprint-FpImage.html">FpImage routines</ulink>.
*/
/**

View File

@@ -37,7 +37,6 @@ typedef enum {
FPI_IMAGE_V_FLIPPED = 1 << 0,
FPI_IMAGE_H_FLIPPED = 1 << 1,
FPI_IMAGE_COLORS_INVERTED = 1 << 2,
FPI_IMAGE_PARTIAL = 1 << 3,
} FpiImageFlags;
/**

View File

@@ -155,6 +155,9 @@ foreach driver: drivers
if driver == 'virtual_image'
drivers_sources += [ 'drivers/virtual-image.c' ]
endif
if driver == 'virtual_device'
drivers_sources += [ 'drivers/virtual-device.c' ]
endif
if driver == 'synaptics'
drivers_sources += [
'drivers/synaptics/synaptics.c',

View File

@@ -1,57 +0,0 @@
diff --git a/libfprint/nbis/mindtct/contour.c b/libfprint/nbis/mindtct/contour.c
index 3e9416c..31f32d0 100644
--- mindtct/contour.c
+++ mindtct/contour.c
@@ -440,6 +440,8 @@ int get_centered_contour(int **ocontour_x, int **ocontour_y,
int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour;
int i, j, ret;
+ g_assert (half_contour > 0);
+
/* Compute maximum length of complete contour */
/* (2 half contours + feature point). */
max_contour = (half_contour<<1) + 1;
diff --git a/libfprint/nbis/mindtct/minutia.c b/libfprint/nbis/mindtct/minutia.c
index 0b29aa0..77cf09d 100644
--- mindtct/minutia.c
+++ mindtct/minutia.c
@@ -1625,7 +1625,7 @@ int process_horizontal_scan_minutia_V2(MINUTIAE *minutiae,
dmapval, bdata, iw, ih, lfsparms);
/* If minuitia IGNORED and not added to the minutia list ... */
- if(ret == IGNORE)
+ if(ret != 0)
/* Deallocate the minutia. */
free_minutia(minutia);
@@ -1776,7 +1776,7 @@ int process_vertical_scan_minutia_V2(MINUTIAE *minutiae,
dmapval, bdata, iw, ih, lfsparms);
/* If minuitia IGNORED and not added to the minutia list ... */
- if(ret == IGNORE)
+ if(ret != 0)
/* Deallocate the minutia. */
free_minutia(minutia);
diff --git a/libfprint/nbis/mindtct/ridges.c b/libfprint/nbis/mindtct/ridges.c
index f0d9cd3..9902585 100644
--- mindtct/ridges.c
+++ mindtct/ridges.c
@@ -147,6 +147,8 @@ int count_minutia_ridges(const int first, MINUTIAE *minutiae,
{
int i, ret, *nbr_list, *nbr_nridges, nnbrs;
+ g_assert (lfsparms->max_nbrs > 0);
+
/* Find up to the maximum number of qualifying neighbors. */
nbr_list = NULL;
if((ret = find_neighbors(&nbr_list, &nnbrs, lfsparms->max_nbrs,
@@ -407,6 +409,8 @@ int insert_neighbor(const int pos, const int nbr_index, const double nbr_dist2,
{
int i;
+ g_assert (pos >= 0);
+
/* If the desired insertion position is beyond one passed the last */
/* neighbor in the lists OR greater than equal to the maximum ... */
/* NOTE: pos is zero-oriented while nnbrs and max_nbrs are 1-oriented. */

View File

@@ -260,8 +260,6 @@ typedef struct g_lfsparms{
int pores_steps_bwd;
double pores_min_dist2;
double pores_max_ratio;
int remove_perimeter_pts;
int min_pp_distance;
/* Ridge Counting Controls */
int max_nbrs;
@@ -611,9 +609,6 @@ typedef struct g_lfsparms{
/* contour points to be considered a pore. */
#define PORES_MAX_RATIO 2.25
/* Points which are closer than this distance to scan perimeter will be removed */
#define PERIMETER_PTS_DISTANCE 10
/***** RIDGE COUNTING CONSTANTS *****/
@@ -1128,9 +1123,6 @@ extern int remove_or_adjust_side_minutiae(MINUTIAE *, unsigned char *,
extern int remove_or_adjust_side_minutiae_V2(MINUTIAE *,
unsigned char *, const int, const int,
int *, const int, const int, const LFSPARMS *);
extern int remove_perimeter_pts(MINUTIAE *minutiae,
unsigned char *bdata, const int iw, const int ih,
const LFSPARMS *lfsparms);
/* results.c */
extern int write_text_results(char *, const int, const int, const int,

View File

@@ -440,8 +440,6 @@ int get_centered_contour(int **ocontour_x, int **ocontour_y,
int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour;
int i, j, ret;
g_assert (half_contour > 0);
/* Compute maximum length of complete contour */
/* (2 half contours + feature point). */
max_contour = (half_contour<<1) + 1;

View File

@@ -150,8 +150,6 @@ LFSPARMS g_lfsparms = {
PORES_STEPS_BWD,
PORES_MIN_DIST2,
PORES_MAX_RATIO,
FALSE, /* not removing perimeter points by default */
PERIMETER_PTS_DISTANCE,
/* Ridge Counting Controls */
MAX_NBRS,
@@ -236,8 +234,6 @@ LFSPARMS g_lfsparms_V2 = {
PORES_STEPS_BWD,
PORES_MIN_DIST2,
PORES_MAX_RATIO,
FALSE, /* not removing perimeter points by default */
PERIMETER_PTS_DISTANCE,
/* Ridge Counting Controls */
MAX_NBRS,

View File

@@ -1625,7 +1625,7 @@ int process_horizontal_scan_minutia_V2(MINUTIAE *minutiae,
dmapval, bdata, iw, ih, lfsparms);
/* If minuitia IGNORED and not added to the minutia list ... */
if(ret != 0)
if(ret == IGNORE)
/* Deallocate the minutia. */
free_minutia(minutia);
@@ -1776,7 +1776,7 @@ int process_vertical_scan_minutia_V2(MINUTIAE *minutiae,
dmapval, bdata, iw, ih, lfsparms);
/* If minuitia IGNORED and not added to the minutia list ... */
if(ret != 0)
if(ret == IGNORE)
/* Deallocate the minutia. */
free_minutia(minutia);

View File

@@ -195,11 +195,6 @@ int remove_false_minutia_V2(MINUTIAE *minutiae,
return(ret);
}
/* 11. Remove minutiae on image edge */
if((ret = remove_perimeter_pts(minutiae, bdata, iw, ih, lfsparms))) {
return (ret);
}
return(0);
}
@@ -1334,159 +1329,6 @@ int remove_pointing_invblock_V2(MINUTIAE *minutiae,
return(0);
}
static void mark_minutiae_in_range(MINUTIAE *minutiae, int *to_remove, int x, int y,
const LFSPARMS *lfsparms)
{
int i, dist;
for (i = 0; i < minutiae->num; i++) {
if (to_remove[i])
continue;
dist = (int)sqrt((x - minutiae->list[i]->x) * (x - minutiae->list[i]->x) +
(y - minutiae->list[i]->y) * (y - minutiae->list[i]->y));
if (dist < lfsparms->min_pp_distance) {
to_remove[i] = 1;
}
}
}
/*************************************************************************
**************************************************************************
#cat: remove_perimeter_pts - Takes a list of true and false minutiae and
#cat: attempts to detect and remove those false minutiae that
#cat: belong to image edge
Input:
minutiae - list of true and false minutiae
bdata - binary image data (0==while & 1==black)
iw - width (in pixels) of image
ih - height (in pixels) of image
lfsparms - parameters and thresholds for controlling LFS
Output:
minutiae - list of pruned minutiae
Return Code:
Zero - successful completion
Negative - system error
**************************************************************************/
int remove_perimeter_pts(MINUTIAE *minutiae,
unsigned char *bdata, const int iw, const int ih,
const LFSPARMS *lfsparms)
{
int i, j, ret, *to_remove;
int *left, *left_up, *left_down;
int *right, *right_up, *right_down;
int removed = 0;
int left_min, right_max;
if (!lfsparms->remove_perimeter_pts)
return(0);
to_remove = calloc(minutiae->num, sizeof(int));
left = calloc(ih, sizeof(int));
left_up = calloc(ih, sizeof(int));
left_down = calloc(ih, sizeof(int));
right = calloc(ih, sizeof(int));
right_up = calloc(ih, sizeof(int));
right_down = calloc(ih, sizeof(int));
/* Pass downwards */
left_min = iw - 1;
right_max = 0;
for (i = 0; i < ih; i++) {
for (j = 0; j < left_min; j++) {
if ((bdata[i * iw + j] != 0)) {
left_min = j;
break;
}
}
if (left_min == (iw - 1))
left_down[i] = -1;
else
left_down[i] = left_min;
for (j = iw - 1; j >= right_max; j--) {
if ((bdata[i * iw + j] != 0)) {
right_max = j;
break;
}
}
if (right_max == 0)
right_down[i] = -1;
else
right_down[i] = right_max;
}
/* Pass upwards */
left_min = iw - 1;
right_max = 0;
for (i = ih - 1; i >= 0; i--) {
for (j = 0; j < left_min; j++) {
if ((bdata[i * iw + j] != 0)) {
left_min = j;
break;
}
}
if (left_min == (iw - 1))
left_up[i] = -1;
else
left_up[i] = left_min;
for (j = iw - 1; j >= right_max; j--) {
if ((bdata[i * iw + j] != 0)) {
right_max = j;
break;
}
}
if (right_max == 0)
right_up[i] = -1;
else
right_up[i] = right_max;
}
/* Merge */
left_min = left_down[ih - 1];
right_max = right_down[ih - 1];
for (i = 0; i < ih; i++) {
if (left_down[i] != left_min)
left[i] = left_down[i];
else
left[i] = left_up[i];
if (right_down[i] != right_max)
right[i] = right_down[i];
else
right[i] = right_up[i];
}
free(left_up);
free(left_down);
free(right_up);
free(right_down);
/* Mark minitiae close to the edge */
for (i = 0; i < ih; i++) {
if (left[i] != -1)
mark_minutiae_in_range(minutiae, to_remove, left[i], i, lfsparms);
if (right[i] != -1)
mark_minutiae_in_range(minutiae, to_remove, right[i], i, lfsparms);
}
free(left);
free(right);
for (i = minutiae->num - 1; i >= 0; i--) {
/* If the current minutia index is flagged for removal ... */
if (to_remove[i]){
removed ++;
/* Remove the minutia from the minutiae list. */
if((ret = remove_minutia(i, minutiae))){
free(to_remove);
return(ret);
}
}
}
free(to_remove);
return (0);
}
/*************************************************************************
**************************************************************************
#cat: remove_overlaps - Takes a list of true and false minutiae and

View File

@@ -147,8 +147,6 @@ int count_minutia_ridges(const int first, MINUTIAE *minutiae,
{
int i, ret, *nbr_list, *nbr_nridges, nnbrs;
g_assert (lfsparms->max_nbrs > 0);
/* Find up to the maximum number of qualifying neighbors. */
nbr_list = NULL;
if((ret = find_neighbors(&nbr_list, &nnbrs, lfsparms->max_nbrs,
@@ -409,8 +407,6 @@ int insert_neighbor(const int pos, const int nbr_index, const double nbr_dist2,
{
int i;
g_assert (pos >= 0);
/* If the desired insertion position is beyond one passed the last */
/* neighbor in the lists OR greater than equal to the maximum ... */
/* NOTE: pos is zero-oriented while nnbrs and max_nbrs are 1-oriented. */

View File

@@ -1,231 +0,0 @@
diff --git nbis/include/lfs.h nbis/include/lfs.h
index f4f38d7..8b12e73 100644
--- nbis/include/lfs.h
+++ nbis/include/lfs.h
@@ -260,6 +260,8 @@ typedef struct g_lfsparms{
int pores_steps_bwd;
double pores_min_dist2;
double pores_max_ratio;
+ int remove_perimeter_pts;
+ int min_pp_distance;
/* Ridge Counting Controls */
int max_nbrs;
@@ -609,6 +611,9 @@ typedef struct g_lfsparms{
/* contour points to be considered a pore. */
#define PORES_MAX_RATIO 2.25
+/* Points which are closer than this distance to scan perimeter will be removed */
+#define PERIMETER_PTS_DISTANCE 10
+
/***** RIDGE COUNTING CONSTANTS *****/
@@ -1123,6 +1128,9 @@ extern int remove_or_adjust_side_minutiae(MINUTIAE *, unsigned char *,
extern int remove_or_adjust_side_minutiae_V2(MINUTIAE *,
unsigned char *, const int, const int,
int *, const int, const int, const LFSPARMS *);
+extern int remove_perimeter_pts(MINUTIAE *minutiae,
+ unsigned char *bdata, const int iw, const int ih,
+ const LFSPARMS *lfsparms);
/* results.c */
extern int write_text_results(char *, const int, const int, const int,
diff --git nbis/mindtct/globals.c nbis/mindtct/globals.c
index da10c15..79bc583 100644
--- nbis/mindtct/globals.c
+++ nbis/mindtct/globals.c
@@ -150,6 +150,8 @@ LFSPARMS g_lfsparms = {
PORES_STEPS_BWD,
PORES_MIN_DIST2,
PORES_MAX_RATIO,
+ FALSE, /* not removing perimeter points by default */
+ PERIMETER_PTS_DISTANCE,
/* Ridge Counting Controls */
MAX_NBRS,
@@ -234,6 +236,8 @@ LFSPARMS g_lfsparms_V2 = {
PORES_STEPS_BWD,
PORES_MIN_DIST2,
PORES_MAX_RATIO,
+ FALSE, /* not removing perimeter points by default */
+ PERIMETER_PTS_DISTANCE,
/* Ridge Counting Controls */
MAX_NBRS,
diff --git nbis/mindtct/remove.c nbis/mindtct/remove.c
index af5ab7d..7311f1c 100644
--- nbis/mindtct/remove.c
+++ nbis/mindtct/remove.c
@@ -195,6 +195,11 @@ int remove_false_minutia_V2(MINUTIAE *minutiae,
return(ret);
}
+ /* 11. Remove minutiae on image edge */
+ if((ret = remove_perimeter_pts(minutiae, bdata, iw, ih, lfsparms))) {
+ return (ret);
+ }
+
return(0);
}
@@ -1329,6 +1334,159 @@ int remove_pointing_invblock_V2(MINUTIAE *minutiae,
return(0);
}
+static void mark_minutiae_in_range(MINUTIAE *minutiae, int *to_remove, int x, int y,
+ const LFSPARMS *lfsparms)
+{
+ int i, dist;
+ for (i = 0; i < minutiae->num; i++) {
+ if (to_remove[i])
+ continue;
+ dist = (int)sqrt((x - minutiae->list[i]->x) * (x - minutiae->list[i]->x) +
+ (y - minutiae->list[i]->y) * (y - minutiae->list[i]->y));
+ if (dist < lfsparms->min_pp_distance) {
+ to_remove[i] = 1;
+ }
+ }
+}
+
+/*************************************************************************
+**************************************************************************
+#cat: remove_perimeter_pts - Takes a list of true and false minutiae and
+#cat: attempts to detect and remove those false minutiae that
+#cat: belong to image edge
+
+ Input:
+ minutiae - list of true and false minutiae
+ bdata - binary image data (0==while & 1==black)
+ iw - width (in pixels) of image
+ ih - height (in pixels) of image
+ lfsparms - parameters and thresholds for controlling LFS
+ Output:
+ minutiae - list of pruned minutiae
+ Return Code:
+ Zero - successful completion
+ Negative - system error
+**************************************************************************/
+int remove_perimeter_pts(MINUTIAE *minutiae,
+ unsigned char *bdata, const int iw, const int ih,
+ const LFSPARMS *lfsparms)
+{
+ int i, j, ret, *to_remove;
+ int *left, *left_up, *left_down;
+ int *right, *right_up, *right_down;
+ int removed = 0;
+ int left_min, right_max;
+
+ if (!lfsparms->remove_perimeter_pts)
+ return(0);
+
+ to_remove = calloc(minutiae->num, sizeof(int));
+ left = calloc(ih, sizeof(int));
+ left_up = calloc(ih, sizeof(int));
+ left_down = calloc(ih, sizeof(int));
+ right = calloc(ih, sizeof(int));
+ right_up = calloc(ih, sizeof(int));
+ right_down = calloc(ih, sizeof(int));
+
+ /* Pass downwards */
+ left_min = iw - 1;
+ right_max = 0;
+ for (i = 0; i < ih; i++) {
+ for (j = 0; j < left_min; j++) {
+ if ((bdata[i * iw + j] != 0)) {
+ left_min = j;
+ break;
+ }
+ }
+ if (left_min == (iw - 1))
+ left_down[i] = -1;
+ else
+ left_down[i] = left_min;
+ for (j = iw - 1; j >= right_max; j--) {
+ if ((bdata[i * iw + j] != 0)) {
+ right_max = j;
+ break;
+ }
+ }
+ if (right_max == 0)
+ right_down[i] = -1;
+ else
+ right_down[i] = right_max;
+ }
+
+ /* Pass upwards */
+ left_min = iw - 1;
+ right_max = 0;
+ for (i = ih - 1; i >= 0; i--) {
+ for (j = 0; j < left_min; j++) {
+ if ((bdata[i * iw + j] != 0)) {
+ left_min = j;
+ break;
+ }
+ }
+ if (left_min == (iw - 1))
+ left_up[i] = -1;
+ else
+ left_up[i] = left_min;
+ for (j = iw - 1; j >= right_max; j--) {
+ if ((bdata[i * iw + j] != 0)) {
+ right_max = j;
+ break;
+ }
+ }
+ if (right_max == 0)
+ right_up[i] = -1;
+ else
+ right_up[i] = right_max;
+ }
+
+ /* Merge */
+ left_min = left_down[ih - 1];
+ right_max = right_down[ih - 1];
+ for (i = 0; i < ih; i++) {
+ if (left_down[i] != left_min)
+ left[i] = left_down[i];
+ else
+ left[i] = left_up[i];
+
+ if (right_down[i] != right_max)
+ right[i] = right_down[i];
+ else
+ right[i] = right_up[i];
+ }
+ free(left_up);
+ free(left_down);
+ free(right_up);
+ free(right_down);
+
+ /* Mark minitiae close to the edge */
+ for (i = 0; i < ih; i++) {
+ if (left[i] != -1)
+ mark_minutiae_in_range(minutiae, to_remove, left[i], i, lfsparms);
+ if (right[i] != -1)
+ mark_minutiae_in_range(minutiae, to_remove, right[i], i, lfsparms);
+ }
+
+ free(left);
+ free(right);
+
+ for (i = minutiae->num - 1; i >= 0; i--) {
+ /* If the current minutia index is flagged for removal ... */
+ if (to_remove[i]){
+ removed ++;
+ /* Remove the minutia from the minutiae list. */
+ if((ret = remove_minutia(i, minutiae))){
+ free(to_remove);
+ return(ret);
+ }
+ }
+ }
+
+ free(to_remove);
+
+ return (0);
+}
+
/*************************************************************************
**************************************************************************
#cat: remove_overlaps - Takes a list of true and false minutiae and

View File

@@ -192,9 +192,3 @@ spatch --sp-file remove-global-y.cocci bozorth3/* include/bozorth.h --in-place
# The above leaves an unused variable around, triggering a warning
# remove it.
patch -p0 < glib-mem-warning.patch
# Also fix some scan-build warnings, mostly by adding assertions
patch -p0 < fix-scan-build-reports.patch
# Add pass to remove perimeter points
patch -p0 < remove-perimeter-pts.patch

View File

@@ -1,5 +1,5 @@
project('libfprint', [ 'c', 'cpp' ],
version: '1.90.2',
version: '1.90.1',
license: 'LGPLv2.1+',
default_options: [
'buildtype=debugoptimized',
@@ -88,7 +88,7 @@ cairo_dep = dependency('cairo', required: false)
# Drivers
drivers = get_option('drivers').split(',')
virtual_drivers = [ 'virtual_image' ]
virtual_drivers = [ 'virtual_image', 'virtual_device' ]
default_drivers = [
'upektc_img',
'vfs5011',

View File

@@ -1,94 +0,0 @@
`umockdev` Tests
================
`umockdev` tests use fingerprint devices mocked by [`umockdev`
toolchain][umockdev].
This document describes how to create a 'capture' test: a test that
captures a picture of a fingerprint from the device (mocked by
`umockdev`) and compares it with the standard one.
Other kinds of `umockdev` tests could be created in a similar manner.
'Capture' Test Creation
-----------------------
A new 'capture' test is created by means of `capture.py` script:
1. Create (if needed) a directory for the driver under `tests`
directory:
`mkdir DRIVER`
2. Prepare your execution environment.
In the next step a working and up to date libfprint is needed. This can be
achieved by installing it into your system. Alternatively, you can set
the following environment variables to run a local build:
- `export LD_PRELOAD=<meson-build-dir>/libfprint/libfprint-2.so`
- `export GI_TYPELIB_PATH=<meson-build-dir>/libfprint`
Also, sometimes the driver must be adopted to the emulated environment
(mainly if it uses random numbers, see `synaptics.c` for an example).
Set the following environment variable to enable this adaptation:
- `export FP_DEVICE_EMULATION=1`
Run the next steps in the same terminal.
3. Find the real USB fingerprint device with `lsusb`, e.g.:
`Bus 001 Device 005: ID 138a:0090 Validity Sensors, Inc. VFS7500 Touch Fingerprint Sensor`
The following USB device is used in the example above:
`/dev/bus/usb/001/005`.
4. Record information about this device:
`umockdev-record /dev/bus/usb/001/005 > DRIVER/device`
5. Record interaction of `capture.py` (or other test) with the device:
`umockdev-record -i /dev/bus/usb/001/005=DRIVER/capture.ioctl -- python3 ./capture.py DRIVER/capture.png`
Files `capture.ioctl` and `capture.png` will be created as the
result of this command.
6. Add driver's name to `drivers_tests` in the `meson.build`.
7. Check whether everything works as expected.
**Note.** To avoid submitting a real fingerprint, the side of finger,
arm, or anything else producing an image with the device can be used.
Possible Issues
---------------
`umockdev-record` aggressively groups URBs. In most cases, manual
intervention is unfortunately required. Often, drivers do a chain of
commands like: A then B each with a different reply. However,
`umockdev-record` could create a file like this:
A
reply 1
reply 2
B
reply 1
reply 2
In that case, records must be re-ordered:
A
reply 1
B
reply 1
A
reply 2
B
reply 2
Other changes may be needed to get everything working. For example the
`elan` driver relies on a timeout that is not reported correctly. In
this case the driver works around it by interpreting the protocol
error differently in the virtual environment (by means of
`FP_DEVICE_EMULATION` environment variable).
[umockdev]: https://github.com/martinpitt/umockdev

View File

@@ -18,9 +18,8 @@ envs.set('NO_AT_BRIDGE', '1')
drivers_tests = [
'elan',
'synaptics',
'vfs0050',
'vfs5011',
'synaptics',
]
if get_option('introspection')

View File

@@ -100,10 +100,7 @@ fpi_device_fake_enroll (FpDevice *device)
fpi_device_get_enroll_data (device, (FpPrint **) &fake_dev->action_data);
if (!print && !fake_dev->ret_error)
{
fpi_device_get_enroll_data (device, &print);
fpi_print_set_type (print, FPI_PRINT_RAW);
}
fpi_device_enroll_complete (device,
print ? g_object_ref (print) : NULL,

View File

@@ -49,7 +49,7 @@ test_context_has_virtual_device (void)
GPtrArray *devices;
unsigned int i;
fpt_setup_virtual_device_environment ();
fpt_setup_virtual_device_environment (FPT_VIRTUAL_DEVICE_IMAGE);
context = fp_context_new ();
devices = fp_context_get_devices (context);
@@ -70,7 +70,7 @@ test_context_has_virtual_device (void)
g_assert_true (FP_IS_DEVICE (virtual_device));
fpt_teardown_virtual_device_environment ();
fpt_teardown_virtual_device_environment (FPT_VIRTUAL_DEVICE_IMAGE);
}
static void
@@ -81,7 +81,7 @@ test_context_enumerates_new_devices (void)
context = fp_context_new ();
fpt_setup_virtual_device_environment ();
fpt_setup_virtual_device_environment (FPT_VIRTUAL_DEVICE_IMAGE);
fp_context_enumerate (context);
devices = fp_context_get_devices (context);
@@ -89,7 +89,7 @@ test_context_enumerates_new_devices (void)
g_assert_nonnull (devices);
g_assert_cmpuint (devices->len, ==, 1);
fpt_teardown_virtual_device_environment ();
fpt_teardown_virtual_device_environment (FPT_VIRTUAL_DEVICE_IMAGE);
}
int

View File

@@ -36,7 +36,7 @@ on_device_opened (FpDevice *dev, GAsyncResult *res, FptContext *tctx)
static void
test_device_open_async (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open (tctx->device, NULL, (GAsyncReadyCallback) on_device_opened, tctx);
@@ -59,7 +59,7 @@ on_device_closed (FpDevice *dev, GAsyncResult *res, FptContext *tctx)
static void
test_device_close_async (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open (tctx->device, NULL, (GAsyncReadyCallback) on_device_opened, tctx);
while (!tctx->user_data)
@@ -76,7 +76,7 @@ static void
test_device_open_sync (void)
{
g_autoptr(GError) error = NULL;
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, &error);
g_assert_no_error (error);
@@ -97,7 +97,7 @@ static void
test_device_open_sync_notify (void)
{
g_autoptr(GError) error = NULL;
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
g_signal_connect (tctx->device, "notify::open", G_CALLBACK (on_open_notify), tctx);
fp_device_open_sync (tctx->device, NULL, &error);
@@ -109,7 +109,7 @@ static void
test_device_close_sync (void)
{
g_autoptr(GError) error = NULL;
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
fp_device_close_sync (tctx->device, NULL, &error);
@@ -131,7 +131,7 @@ static void
test_device_close_sync_notify (void)
{
g_autoptr(GError) error = NULL;
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
@@ -144,7 +144,7 @@ test_device_close_sync_notify (void)
static void
test_device_get_driver (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
g_assert_cmpstr (fp_device_get_driver (tctx->device), ==, "virtual_image");
@@ -153,7 +153,7 @@ test_device_get_driver (void)
static void
test_device_get_device_id (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
g_assert_cmpstr (fp_device_get_device_id (tctx->device), ==, "0");
@@ -162,7 +162,7 @@ test_device_get_device_id (void)
static void
test_device_get_name (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
g_assert_cmpstr (fp_device_get_name (tctx->device), ==,
@@ -172,7 +172,7 @@ test_device_get_name (void)
static void
test_device_get_scan_type (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
g_assert_cmpint (fp_device_get_scan_type (tctx->device), ==, FP_SCAN_TYPE_SWIPE);
@@ -181,7 +181,7 @@ test_device_get_scan_type (void)
static void
test_device_get_nr_enroll_stages (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
g_assert_cmpuint (fp_device_get_nr_enroll_stages (tctx->device), ==, 5);
@@ -190,7 +190,7 @@ test_device_get_nr_enroll_stages (void)
static void
test_device_supports_identify (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
g_assert_true (fp_device_supports_identify (tctx->device));
@@ -199,7 +199,7 @@ test_device_supports_identify (void)
static void
test_device_supports_capture (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
g_assert_true (fp_device_supports_capture (tctx->device));
@@ -208,7 +208,7 @@ test_device_supports_capture (void)
static void
test_device_has_storage (void)
{
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_imgdev ();
g_autoptr(FptContext) tctx = fpt_context_new_with_virtual_device (FPT_VIRTUAL_DEVICE_IMAGE);
fp_device_open_sync (tctx->device, NULL, NULL);
g_assert_false (fp_device_has_storage (tctx->device));

View File

@@ -81,8 +81,6 @@ test_frame_assembling (void)
ctx.frame_height = 20;
ctx.image_width = width;
g_assert (height > ctx.frame_height);
offset = 10;
test_height = height - (height - ctx.frame_height) % offset;

View File

@@ -541,24 +541,6 @@ test_driver_enroll_error_no_print (void)
g_assert_true (error == g_steal_pointer (&fake_dev->ret_error));
g_assert_null (out_print);
g_assert_null (fake_dev->ret_print);
g_clear_error (&error);
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"*Driver did not set the type on the returned print*");
fake_dev->ret_error = NULL;
fake_dev->ret_print = fp_print_new (device); /* Type not set. */
g_object_add_weak_pointer (G_OBJECT (fake_dev->ret_print),
(gpointer) (&fake_dev->ret_print));
out_print =
fp_device_enroll_sync (device, fp_print_new (device), NULL, NULL, NULL, &error);
g_test_assert_expected_messages ();
g_assert (fake_dev->last_called_function == dev_class->enroll);
g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL);
g_assert_null (out_print);
g_assert_null (fake_dev->ret_print);
g_clear_error (&error);
}
typedef struct
@@ -653,7 +635,7 @@ test_driver_enroll_progress (void)
{
g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class ();
g_autoptr(FpAutoCloseDevice) device = NULL;
G_GNUC_UNUSED g_autoptr(FpPrint) enrolled_print = NULL;
g_autoptr(FpPrint) enrolled_print = NULL;
ExpectedEnrollData expected_enroll_data = {0};
FpiDeviceFake *fake_dev;
@@ -1373,7 +1355,7 @@ test_driver_identify_report_no_callback (void)
g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1);
g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref);
g_autoptr(FpAutoCloseDevice) device = NULL;
G_GNUC_UNUSED g_autoptr(FpPrint) enrolled_print = NULL;
g_autoptr(FpPrint) enrolled_print = NULL;
g_autoptr(FpPrint) print = NULL;
g_autoptr(FpPrint) match = NULL;
g_autoptr(GError) error = NULL;
@@ -1511,7 +1493,6 @@ test_driver_list (void)
g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref);
unsigned int i;
@@ -1534,7 +1515,6 @@ test_driver_list_error (void)
g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new ();
FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
g_autoptr(GPtrArray) prints = NULL;
fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
@@ -1911,7 +1891,7 @@ test_driver_action_error_all (void)
g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref);
g_autoptr(GError) error = NULL;
FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
FpiDeviceFake *fake_dev;
FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
fake_dev = FPI_DEVICE_FAKE (device);
fake_dev->return_action_error = TRUE;
@@ -1982,7 +1962,7 @@ test_driver_action_error_fallback_all (void)
g_autoptr(GPtrArray) prints = g_ptr_array_new_with_free_func (g_object_unref);
g_autoptr(GError) error = NULL;
FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device);
FpiDeviceFake *fake_dev;
FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device);
fake_dev = FPI_DEVICE_FAKE (device);
fake_dev->return_action_error = TRUE;

View File

@@ -200,7 +200,7 @@ test_ssm_new_full (void)
static void
test_ssm_new_no_handler (void)
{
G_GNUC_UNUSED g_autoptr(FpiSsm) ssm = NULL;
g_autoptr(FpiSsm) ssm = NULL;
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*BUG:*handler*");
@@ -211,7 +211,7 @@ test_ssm_new_no_handler (void)
static void
test_ssm_new_wrong_states (void)
{
G_GNUC_UNUSED g_autoptr(FpiSsm) ssm = NULL;
g_autoptr(FpiSsm) ssm = NULL;
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*BUG:*nr_states*");
@@ -1172,7 +1172,6 @@ test_ssm_subssm_start (void)
g_autoptr(FpiSsm) subssm =
ssm_test_new_full (FPI_TEST_SSM_STATE_NUM, "FPI_TEST_SUB_SSM");
FpiSsmTestData *data = fpi_ssm_get_data (ssm);
g_autoptr(FpiSsmTestData) subdata =
fpi_ssm_test_data_ref (fpi_ssm_get_data (subssm));
@@ -1263,7 +1262,6 @@ test_ssm_subssm_start_with_started (void)
g_autoptr(FpiSsm) subssm =
ssm_test_new_full (FPI_TEST_SSM_STATE_NUM, "FPI_TEST_SUB_SSM");
FpiSsmTestData *data = fpi_ssm_get_data (ssm);
g_autoptr(FpiSsmTestData) subdata =
fpi_ssm_test_data_ref (fpi_ssm_get_data (subssm));
@@ -1307,7 +1305,6 @@ test_ssm_subssm_start_with_delayed (void)
g_autoptr(FpiSsm) subssm =
ssm_test_new_full (FPI_TEST_SSM_STATE_NUM, "FPI_TEST_SUB_SSM");
FpiSsmTestData *data = fpi_ssm_get_data (ssm);
g_autoptr(FpiSsmTestData) subdata =
fpi_ssm_test_data_ref (fpi_ssm_get_data (subssm));
gpointer timeout_tracker = GUINT_TO_POINTER (TRUE);

View File

@@ -22,41 +22,60 @@
#include "test-utils.h"
void
fpt_teardown_virtual_device_environment (void)
struct
{
const char *path = g_getenv ("FP_VIRTUAL_IMAGE");
const char *envvar;
const char *driver_id;
const char *device_id;
} devtype_vars[FPT_NUM_VIRTUAL_DEVICE_TYPES] = {
{ "FP_VIRTUAL_IMAGE", "virtual_image", "virtual_image" }, /* FPT_VIRTUAL_DEVICE_IMAGE */
{ "FP_VIRTUAL_DEVICE", "virtual_device", "virtual_device" }, /* FPT_VIRTUAL_DEVICE */
{ "FP_VIRTUAL_DEVICE_IDENT", "virtual_device", "virtual_device_ident" } /* FPT_VIRTUAL_DEVICE_IDENT */
};
void
fpt_teardown_virtual_device_environment (FptVirtualDeviceType devtype)
{
const char *path;
path = g_getenv (devtype_vars[devtype].envvar);
if (path)
{
g_autofree char *temp_dir = g_path_get_dirname (path);
g_unsetenv ("FP_VIRTUAL_IMAGE");
g_unsetenv (devtype_vars[devtype].envvar);
g_unlink (path);
g_rmdir (temp_dir);
}
}
static FptVirtualDeviceType global_devtype;
static void
on_signal_event (int sig)
{
fpt_teardown_virtual_device_environment ();
fpt_teardown_virtual_device_environment (global_devtype);
}
void
fpt_setup_virtual_device_environment (void)
fpt_setup_virtual_device_environment (FptVirtualDeviceType devtype)
{
g_autoptr(GError) error = NULL;
g_autofree char *temp_dir = NULL;
g_autofree char *temp_path = NULL;
g_autofree char *filename = NULL;
g_assert_null (g_getenv ("FP_VIRTUAL_IMAGE"));
g_assert_null (g_getenv (devtype_vars[devtype].envvar));
temp_dir = g_dir_make_tmp ("libfprint-XXXXXX", &error);
g_assert_no_error (error);
temp_path = g_build_filename (temp_dir, "virtual-image.socket", NULL);
g_setenv ("FP_VIRTUAL_IMAGE", temp_path, TRUE);
filename = g_strdup_printf ("%s.socket", devtype_vars[devtype].device_id);
temp_path = g_build_filename (temp_dir, filename, NULL);
g_setenv (devtype_vars[devtype].envvar, temp_path, TRUE);
global_devtype = devtype;
signal (SIGKILL, on_signal_event);
signal (SIGABRT, on_signal_event);
@@ -78,13 +97,16 @@ fpt_context_new (void)
}
FptContext *
fpt_context_new_with_virtual_imgdev (void)
fpt_context_new_with_virtual_device (FptVirtualDeviceType devtype)
{
FptContext *tctx;
GPtrArray *devices;
unsigned int i;
fpt_setup_virtual_device_environment ();
g_assert_true (devtype >= FPT_VIRTUAL_DEVICE_IMAGE &&
devtype < FPT_NUM_VIRTUAL_DEVICE_TYPES);
fpt_setup_virtual_device_environment (devtype);
tctx = fpt_context_new ();
devices = fp_context_get_devices (tctx->fp_context);
@@ -96,7 +118,7 @@ fpt_context_new_with_virtual_imgdev (void)
{
FpDevice *device = devices->pdata[i];
if (g_strcmp0 (fp_device_get_driver (device), "virtual_image") == 0)
if (g_strcmp0 (fp_device_get_driver (device), devtype_vars[devtype].driver_id) == 0)
{
tctx->device = device;
break;
@@ -105,6 +127,7 @@ fpt_context_new_with_virtual_imgdev (void)
g_assert_true (FP_IS_DEVICE (tctx->device));
g_object_add_weak_pointer (G_OBJECT (tctx->device), (gpointer) & tctx->device);
g_object_set_data (G_OBJECT (tctx->fp_context), "devtype", GUINT_TO_POINTER (devtype));
return tctx;
}
@@ -112,6 +135,10 @@ fpt_context_new_with_virtual_imgdev (void)
void
fpt_context_free (FptContext *tctx)
{
FptVirtualDeviceType devtype;
devtype = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (tctx->fp_context), "devtype"));
if (tctx->device && fp_device_is_open (tctx->device))
{
g_autoptr(GError) error = NULL;
@@ -123,5 +150,5 @@ fpt_context_free (FptContext *tctx)
g_clear_object (&tctx->fp_context);
g_free (tctx);
fpt_teardown_virtual_device_environment ();
fpt_teardown_virtual_device_environment (devtype);
}

View File

@@ -19,8 +19,15 @@
#include <glib.h>
void fpt_setup_virtual_device_environment (void);
void fpt_teardown_virtual_device_environment (void);
typedef enum {
FPT_VIRTUAL_DEVICE_IMAGE = 0,
FPT_VIRTUAL_DEVICE,
FPT_VIRTUAL_DEVICE_IDENTIFY,
FPT_NUM_VIRTUAL_DEVICE_TYPES
} FptVirtualDeviceType;
void fpt_setup_virtual_device_environment (FptVirtualDeviceType devtype);
void fpt_teardown_virtual_device_environment (FptVirtualDeviceType devtype);
typedef struct _FptContext
{
@@ -30,7 +37,7 @@ typedef struct _FptContext
} FptContext;
FptContext * fpt_context_new (void);
FptContext * fpt_context_new_with_virtual_imgdev (void);
FptContext * fpt_context_new_with_virtual_device (FptVirtualDeviceType devtype);
void fpt_context_free (FptContext *test_context);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

View File

@@ -1,83 +0,0 @@
P: /devices/pci0000:00/0000:00:14.0/usb1/1-9
N: bus/usb/001/004=12011001FF10FF088A13500060000000010109022E00010100A0320904000004FF00000007050102400000070581024000000705820240000007058303080004
E: DEVNAME=/dev/bus/usb/001/004
E: DEVTYPE=usb_device
E: DRIVER=usb
E: PRODUCT=138a/50/60
E: TYPE=255/16/255
E: BUSNUM=001
E: DEVNUM=004
E: MAJOR=189
E: MINOR=3
E: SUBSYSTEM=usb
E: ID_VENDOR=138a
E: ID_VENDOR_ENC=138a
E: ID_VENDOR_ID=138a
E: ID_MODEL=0050
E: ID_MODEL_ENC=0050
E: ID_MODEL_ID=0050
E: ID_REVISION=0060
E: ID_SERIAL=138a_0050_6d1900a1a0c0
E: ID_SERIAL_SHORT=6d1900a1a0c0
E: ID_BUS=usb
E: ID_USB_INTERFACES=:ff0000:
E: ID_VENDOR_FROM_DATABASE=Validity Sensors, Inc.
E: ID_MODEL_FROM_DATABASE=Swipe Fingerprint Sensor
E: ID_PATH=pci-0000:00:14.0-usb-0:9
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_9
E: LIBFPRINT_DRIVER=Validity VFS0050
A: authorized=1
A: avoid_reset_quirk=0
A: bConfigurationValue=1
A: bDeviceClass=ff
A: bDeviceProtocol=ff
A: bDeviceSubClass=10
A: bMaxPacketSize0=8
A: bMaxPower=100mA
A: bNumConfigurations=1
A: bNumInterfaces= 1
A: bcdDevice=0060
A: bmAttributes=a0
A: busnum=1
A: configuration=
H: descriptors=12011001FF10FF088A13500060000000010109022E00010100A0320904000004FF00000007050102400000070581024000000705820240000007058303080004
A: dev=189:3
A: devnum=4
A: devpath=9
L: driver=../../../../../bus/usb/drivers/usb
A: idProduct=0050
A: idVendor=138a
A: ltm_capable=no
A: maxchild=0
L: port=../1-0:1.0/usb1-port9
A: power/active_duration=19312
A: power/async=enabled
A: power/autosuspend=2
A: power/autosuspend_delay_ms=2000
A: power/connected_duration=1037732
A: power/control=auto
A: power/level=auto
A: power/persist=1
A: power/runtime_active_kids=0
A: power/runtime_active_time=19151
A: power/runtime_enabled=enabled
A: power/runtime_status=active
A: power/runtime_suspended_time=1018305
A: power/runtime_usage=0
A: power/wakeup=disabled
A: power/wakeup_abort_count=
A: power/wakeup_active=
A: power/wakeup_active_count=
A: power/wakeup_count=
A: power/wakeup_expire_count=
A: power/wakeup_last_time_ms=
A: power/wakeup_max_time_ms=
A: power/wakeup_total_time_ms=
A: quirks=0x0
A: removable=fixed
A: rx_lanes=1
A: serial=6d1900a1a0c0
A: speed=12
A: tx_lanes=1
A: urbnum=8
A: version= 1.10