From 61f93ce92e3597903c28990f3af0330fec1cdc09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 17 Aug 2023 20:38:26 +0200 Subject: [PATCH 01/79] ci: Move build dir during installed tests so we check that we don't use built artifacts --- .gitlab-ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3635d4a0..ce9ae2da 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -126,17 +126,18 @@ test_installed: script: - meson setup _build --prefix=/usr -Ddrivers=all - meson install -C _build + - mv _build _build_dir - gnome-desktop-testing-runner --list libfprint-2 - gnome-desktop-testing-runner libfprint-2 - --report-directory=_build/installed-tests-report/failed/ - --log-directory=_build/installed-tests-report/logs/ + --report-directory=_installed-tests-report/failed/ + --log-directory=_installed-tests-report/logs/ --parallel=0 artifacts: expose_as: 'GNOME Tests Runner logs' when: always paths: - - _build/meson-logs - - _build/installed-tests-report + - _build_dir/meson-logs + - _installed-tests-report expire_in: 1 week From eac5c1a9d49c03d599511d5c1bfa49bc5e181858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 17 Aug 2023 20:50:09 +0200 Subject: [PATCH 02/79] tests: Install current TOD driver in installed tests and use it --- tests/meson.build | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index fe87ef0f..5058ae31 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -131,6 +131,7 @@ if get_option('introspection') 'LD_LIBRARY_PATH=' + installed_tests_libdir, 'FP_PRINTS_PATH=' + installed_tests_testdir / 'prints', # FIXME: Adding this requires gnome-desktop-testing!12 + # 'GI_TYPELIB_PATH=' + installed_tests_libdir / 'girepository-1.0', ]), 'extra_content': '', }, @@ -389,7 +390,8 @@ if get_option('tod') ], include_directories: include_directories('../libfprint'), dependencies: deps, - install: false + install: installed_tests, + install_dir: installed_tests_execdir / 'tod-drivers' / 'current', ) fp_todv1_enums = gnome.mkenums_simple('fp-todv1-enums', @@ -494,8 +496,10 @@ if get_option('tod') endif tod_test_envs = tod_envs tod_test_envs.prepend('FP_DRIVERS_WHITELIST', tod_driver) - tod_test_envs.set('FP_TOD_DRIVERS_DIR', meson.current_source_dir() / - tod_driver_info.get('tod-dir')) + tod_test_envs.set('FP_TOD_DRIVERS_DIR', + tod_driver_info.get('tod-dir') == meson.current_build_dir() ? + meson.current_build_dir() : + meson.current_source_dir() / tod_driver_info.get('tod-dir')) tod_test_envs.set('FP_TOD_TEST_DRIVER_NAME', tod_driver) test(tod_test_name + '-' + tod_driver, @@ -520,7 +524,8 @@ if get_option('tod') 'env': ' '.join([ tod_envs_str, 'FP_TOD_DRIVERS_DIR=' + installed_tests_execdir / - tod_driver_info.get('tod-dir'), + (tod_driver_info.get('tod-dir') == meson.current_build_dir() ? + 'tod-drivers' / 'current' : tod_driver_info.get('tod-dir')), ]), 'extra_content': '', }, From 9f235e2121b98dc9ab3f4aa14a92fac644acfac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 17 Aug 2023 21:17:26 +0200 Subject: [PATCH 03/79] tests: Use native GTest utils to generate assets names --- tests/meson.build | 14 +++++++++----- tests/test-fpi-assembling.c | 4 +--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index 5058ae31..2832158e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -6,6 +6,8 @@ envs.set('G_MESSAGES_DEBUG', 'all') # Setup paths envs.set('MESON_SOURCE_ROOT', meson.project_source_root()) envs.set('MESON_BUILD_ROOT', meson.project_build_root()) +envs.set('G_TEST_SRCDIR', meson.current_source_dir()) +envs.set('G_TEST_BUILDDIR', meson.current_build_dir()) envs.prepend('LD_LIBRARY_PATH', meson.project_build_root() / 'libfprint') # Set FP_DEVICE_EMULATION so that drivers can adapt (e.g. to use fixed @@ -71,6 +73,12 @@ envs_str = run_command(python3, '-c', env_parser_cmd, env: envs, check: installed_tests).stdout().strip() +envs_str = ' '.join([ + envs_str, + 'G_TEST_SRCDIR=' + installed_tests_testdir, + 'G_TEST_BUILDDIR=' + installed_tests_execdir, +]) + if get_option('introspection') envs.prepend('GI_TYPELIB_PATH', meson.project_build_root() / 'libfprint') virtual_devices_tests = [ @@ -247,10 +255,6 @@ endif unit_tests_deps = { 'fpi-assembling' : [cairo_dep] } -test_config = configuration_data() -test_config.set_quoted('SOURCE_ROOT', meson.project_source_root()) -test_config_h = configure_file(output: 'test-config.h', configuration: test_config) - foreach test_name: unit_tests if unit_tests_deps.has_key(test_name) missing_deps = false @@ -278,7 +282,7 @@ foreach test_name: unit_tests basename = 'test-' + test_name test_exe = executable(basename, - sources: [basename + '.c', test_config_h], + sources: basename + '.c', dependencies: [ libfprint_private_dep ] + extra_deps, c_args: common_cflags, link_whole: test_utils, diff --git a/tests/test-fpi-assembling.c b/tests/test-fpi-assembling.c index 94b8fe5e..c6dae6fe 100644 --- a/tests/test-fpi-assembling.c +++ b/tests/test-fpi-assembling.c @@ -22,7 +22,6 @@ #include #include "fpi-assembling.h" #include "fpi-image.h" -#include "test-config.h" typedef struct { @@ -67,8 +66,7 @@ test_frame_assembling (void) g_autoptr(FpImage) fp_img = NULL; GSList *frames = NULL; - g_assert_false (SOURCE_ROOT == NULL); - path = g_build_path (G_DIR_SEPARATOR_S, SOURCE_ROOT, "tests", "vfs5011", "capture.png", NULL); + path = g_test_build_filename (G_TEST_DIST, "vfs5011", "capture.png", NULL); img = cairo_image_surface_create_from_png (path); data = cairo_image_surface_get_data (img); From 6607d9f10b83aaf440001b75845f0a3faa8c2431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 21 Aug 2023 12:22:07 +0200 Subject: [PATCH 04/79] tests: Use tests execdir install rpath --- tests/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/meson.build b/tests/meson.build index 2832158e..e95c1cfb 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -396,6 +396,7 @@ if get_option('tod') dependencies: deps, install: installed_tests, install_dir: installed_tests_execdir / 'tod-drivers' / 'current', + install_rpath: installed_tests_execdir, ) fp_todv1_enums = gnome.mkenums_simple('fp-todv1-enums', From 2aeec32558bad967e2c50dea57c75b0f8be5b992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 21 Aug 2023 12:24:36 +0200 Subject: [PATCH 05/79] tests/tod-drivers: Remove RPATH information from libraries --- ...vice-fake-tod-ssm-test-v1+1.90.1-x86_64.so | Bin 123280 -> 123280 bytes ...e-fake-tod-test-driver-v1+1.90.1-x86_64.so | Bin 40768 -> 40768 bytes ...vice-fake-tod-ssm-test-v1+1.90.2-x86_64.so | Bin 123280 -> 123280 bytes ...e-fake-tod-test-driver-v1+1.90.2-x86_64.so | Bin 40792 -> 40792 bytes ...vice-fake-tod-ssm-test-v1+1.90.3-x86_64.so | Bin 123320 -> 123320 bytes ...e-fake-tod-test-driver-v1+1.90.3-x86_64.so | Bin 40792 -> 40792 bytes ...vice-fake-tod-ssm-test-v1+1.90.5-x86_64.so | Bin 123360 -> 123360 bytes ...e-fake-tod-test-driver-v1+1.90.5-x86_64.so | Bin 40792 -> 40792 bytes ...vice-fake-tod-ssm-test-v1+1.94.0-x86_64.so | Bin 125168 -> 125168 bytes ...e-fake-tod-test-driver-v1+1.94.0-x86_64.so | Bin 42912 -> 42912 bytes ...vice-fake-tod-ssm-test-v1+1.94.3-x86_64.so | Bin 131584 -> 131584 bytes ...e-fake-tod-test-driver-v1+1.94.3-x86_64.so | Bin 44968 -> 44968 bytes 12 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-ssm-test-v1+1.90.1-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-ssm-test-v1+1.90.1-x86_64.so index 094bb567e718fd1ff4bf3c10ff754f16cd90362c..626072f63317360d0d65b44b610356b1d05ae75e 100755 GIT binary patch delta 76 jcmbPmn0>-w_6_rSO&KD{0Fxb=M4DglZhyhcxZ*MZ8EG4^ delta 32 qcmV+*0N?+R!v~PV2e6+Fm+yiBA(xPY0Th!#0u+Pj4Y%kG0jSgCBMpQA diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-test-driver-v1+1.90.1-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-test-driver-v1+1.90.1-x86_64.so index 05c265b0c9302553f1bb921c1dc75eb418985cc3..a745658a239562bfe28e8d767fd51ffee88c2e94 100755 GIT binary patch delta 68 bcmX@GkLkcZrVX~-rVJ5efXzkR=OzOHNeCF9 delta 39 vcmX@GkLkcZrVX~-lO=N$MRGEe^eZgP;>}ESb29TvD|FNIN;emApPLK-Bo+?9 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-ssm-test-v1+1.90.2-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-ssm-test-v1+1.90.2-x86_64.so index 79dd107351aed55d755faa5d61b53dc4366a27e7..bc28f12c7fefa65c8f1d8b12b933fcbe2f975bb5 100755 GIT binary patch delta 76 jcmbPmn0>-w_6_rSO&KD{0Fxb=M4DglZhyhcxZ*MZ8EG4^ delta 32 qcmV+*0N?+R!v~PV2e6+Fm+yiBA(xPY0Th!#0u+Pj4Y%kG0jSgCBMpQA diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-test-driver-v1+1.90.2-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-test-driver-v1+1.90.2-x86_64.so index ead8c9752b0ae58f90f55438f6071162b0ba6c81..ae5664c5a6e9f59eceedcec34d1ed6ccf2b6c4f8 100755 GIT binary patch delta 68 bcmcbykLkufrVX~-rVJ5efXzkR&n5!^Rj?T6 delta 39 vcmcbykLkufrVX~-ljU<1MRGEe^eZgP;>}ESb29TvD|FNIN;emAKbs5yEBp@* diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-ssm-test-v1+1.90.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-ssm-test-v1+1.90.3-x86_64.so index 336ced5353b04f2fb950346bc96e90ab2e013ae0..2ec3af544d4b0d7f47b22cdfa8388b2b32efc3ed 100755 GIT binary patch delta 76 jcmdmSn0?1#_6_rSO&KD{0Fxb=M4DglZhyhcc;PYtF##MX delta 48 zcmdmSn0?1#_6_rSr)xDcDvIP}Ch1pLn8ll!=;mbRl~(Ac=ao)&WD;q9!MpthFXM&F E0KHNYb^rhX diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-test-driver-v1+1.90.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-test-driver-v1+1.90.3-x86_64.so index 281bb0968d93465195d346ba91d5b6945a07e8b1..f9fe2b3beb3b7693ce05876fc51aad3224953566 100755 GIT binary patch delta 68 bcmcbykLkufrVX~-rVJ5efXzkR&n5!^Rj?T6 delta 39 vcmcbykLkufrVX~-ljU<1MRGEe^eZgP;>}ESb29TvD|FNIN;emAKbs5yEBp@* diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.5/libdevice-fake-tod-ssm-test-v1+1.90.5-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.5/libdevice-fake-tod-ssm-test-v1+1.90.5-x86_64.so index 0001228a8c3453d0d44c3773b715eafd8d937562..f7a74067b4fd40b18689989463f051fabe42c8d8 100755 GIT binary patch delta 76 jcmaEGnEk}ESb29TvD|FNIN;emAKbs5yEBp@* diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-ssm-test-v1+1.94.0-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-ssm-test-v1+1.94.0-x86_64.so index 49e3104aa3636c1abdc0849d8fc6087979f5b0f7..f430aeb1422399ec035924ffa5068a0b1678be9d 100755 GIT binary patch delta 76 jcmexxk^RF(_6_oUrVJ5efXR+bV$E56+q3u>+in2>Fq0bk delta 48 zcmexxk^RF(_6_oU(=WC%DvIP}Ch1pLn8ll!=;mbRl~(Ac=ao)&WD;x6;@h6Z$JllY E0Mv{T@c;k- diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-test-driver-v1+1.94.0-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-test-driver-v1+1.94.0-x86_64.so index 8c64885eb8be3ad14574933039d61eee19a42811..9afb57d94bfe3d1e3e9eb36abf71558bbf8c15a7 100755 GIT binary patch delta 68 bcmZ2*o@v2(rVX_`rVJ5efX$nD-pv94QQ;XX delta 39 vcmZ2*o@v2(rVX_`ler5NMRGEe^eZgP;>}ESb29TvD|FNIN;hxfc{d9HC(;i= diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so index b8934d1a75fae9713f7e9e6a78a3cf5469e71d43..31108c256f25046f0cfa3a9bf408012d66e4adf9 100755 GIT binary patch delta 37 ecmZo@;b>^#*zlfLh#>+UG&AyTXXIo2{}}-D#0>fX delta 37 scmZo@;b>^#*zlfLNI$=zL_a09OyAwl-`7$Q&td@7APfxv delta 33 pcmZ2+pJ~N?rVU;^Li+gyCHg6;W%}-Z{=TmIIhjeDV|aWP0|41=3@`uy From 54f02c77991506bd28d375a19ccdac49a12ba9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 21 Aug 2023 12:42:02 +0200 Subject: [PATCH 06/79] tests: Remove executable bits from installed libraries --- .../libdevice-fake-tod-ssm-test-v1+1.90.1-x86_64.so | Bin ...bdevice-fake-tod-test-driver-v1+1.90.1-x86_64.so | Bin .../libdevice-fake-tod-ssm-test-v1+1.90.2-x86_64.so | Bin ...bdevice-fake-tod-test-driver-v1+1.90.2-x86_64.so | Bin .../libdevice-fake-tod-ssm-test-v1+1.90.3-x86_64.so | Bin ...bdevice-fake-tod-test-driver-v1+1.90.3-x86_64.so | Bin .../libdevice-fake-tod-ssm-test-v1+1.90.5-x86_64.so | Bin ...bdevice-fake-tod-test-driver-v1+1.90.5-x86_64.so | Bin .../libdevice-fake-tod-ssm-test-v1+1.94.0-x86_64.so | Bin ...bdevice-fake-tod-test-driver-v1+1.94.0-x86_64.so | Bin .../libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so | Bin ...bdevice-fake-tod-test-driver-v1+1.94.3-x86_64.so | Bin 12 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-ssm-test-v1+1.90.1-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-test-driver-v1+1.90.1-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-ssm-test-v1+1.90.2-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-test-driver-v1+1.90.2-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-ssm-test-v1+1.90.3-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-test-driver-v1+1.90.3-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.90.5/libdevice-fake-tod-ssm-test-v1+1.90.5-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.90.5/libdevice-fake-tod-test-driver-v1+1.90.5-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-ssm-test-v1+1.94.0-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-test-driver-v1+1.94.0-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so mode change 100755 => 100644 tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-test-driver-v1+1.94.3-x86_64.so diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-ssm-test-v1+1.90.1-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-ssm-test-v1+1.90.1-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-test-driver-v1+1.90.1-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.1/libdevice-fake-tod-test-driver-v1+1.90.1-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-ssm-test-v1+1.90.2-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-ssm-test-v1+1.90.2-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-test-driver-v1+1.90.2-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.2/libdevice-fake-tod-test-driver-v1+1.90.2-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-ssm-test-v1+1.90.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-ssm-test-v1+1.90.3-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-test-driver-v1+1.90.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.3/libdevice-fake-tod-test-driver-v1+1.90.3-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.5/libdevice-fake-tod-ssm-test-v1+1.90.5-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.5/libdevice-fake-tod-ssm-test-v1+1.90.5-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.90.5/libdevice-fake-tod-test-driver-v1+1.90.5-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.90.5/libdevice-fake-tod-test-driver-v1+1.90.5-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-ssm-test-v1+1.94.0-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-ssm-test-v1+1.94.0-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-test-driver-v1+1.94.0-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-test-driver-v1+1.94.0-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so old mode 100755 new mode 100644 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-test-driver-v1+1.94.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-test-driver-v1+1.94.3-x86_64.so old mode 100755 new mode 100644 From 1372d6f0815f3358ddc1cf4ec831ed2b254a1efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 17 Aug 2023 21:17:26 +0200 Subject: [PATCH 07/79] tests: Use native GTest utils to generate assets names --- tests/meson.build | 14 +++++++++----- tests/test-fpi-assembling.c | 4 +--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index b172c1ae..31040d1d 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -6,6 +6,8 @@ envs.set('G_MESSAGES_DEBUG', 'all') # Setup paths envs.set('MESON_SOURCE_ROOT', meson.project_source_root()) envs.set('MESON_BUILD_ROOT', meson.project_build_root()) +envs.set('G_TEST_SRCDIR', meson.current_source_dir()) +envs.set('G_TEST_BUILDDIR', meson.current_build_dir()) envs.prepend('LD_LIBRARY_PATH', meson.project_build_root() / 'libfprint') # Set FP_DEVICE_EMULATION so that drivers can adapt (e.g. to use fixed @@ -71,6 +73,12 @@ envs_str = run_command(python3, '-c', env_parser_cmd, env: envs, check: installed_tests).stdout().strip() +envs_str = ' '.join([ + envs_str, + 'G_TEST_SRCDIR=' + installed_tests_testdir, + 'G_TEST_BUILDDIR=' + installed_tests_execdir, +]) + if get_option('introspection') envs.prepend('GI_TYPELIB_PATH', meson.project_build_root() / 'libfprint') virtual_devices_tests = [ @@ -247,10 +255,6 @@ endif unit_tests_deps = { 'fpi-assembling' : [cairo_dep] } -test_config = configuration_data() -test_config.set_quoted('SOURCE_ROOT', meson.project_source_root()) -test_config_h = configure_file(output: 'test-config.h', configuration: test_config) - foreach test_name: unit_tests if unit_tests_deps.has_key(test_name) missing_deps = false @@ -278,7 +282,7 @@ foreach test_name: unit_tests basename = 'test-' + test_name test_exe = executable(basename, - sources: [basename + '.c', test_config_h], + sources: basename + '.c', dependencies: [ libfprint_private_dep ] + extra_deps, c_args: common_cflags, link_whole: test_utils, diff --git a/tests/test-fpi-assembling.c b/tests/test-fpi-assembling.c index 94b8fe5e..c6dae6fe 100644 --- a/tests/test-fpi-assembling.c +++ b/tests/test-fpi-assembling.c @@ -22,7 +22,6 @@ #include #include "fpi-assembling.h" #include "fpi-image.h" -#include "test-config.h" typedef struct { @@ -67,8 +66,7 @@ test_frame_assembling (void) g_autoptr(FpImage) fp_img = NULL; GSList *frames = NULL; - g_assert_false (SOURCE_ROOT == NULL); - path = g_build_path (G_DIR_SEPARATOR_S, SOURCE_ROOT, "tests", "vfs5011", "capture.png", NULL); + path = g_test_build_filename (G_TEST_DIST, "vfs5011", "capture.png", NULL); img = cairo_image_surface_create_from_png (path); data = cairo_image_surface_get_data (img); From b924d715c964eb229bb1d0fd21d7084f4b4efdec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 17 Aug 2023 20:38:26 +0200 Subject: [PATCH 08/79] ci: Move build dir during installed tests so we check that we don't use built artifacts --- .gitlab-ci.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fd03751e..590eb203 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -125,17 +125,19 @@ test_installed: script: - meson setup _build --prefix=/usr -Ddrivers=all - meson install -C _build + - mv _build _build_dir + - rm -rf tests - gnome-desktop-testing-runner --list libfprint-2 - gnome-desktop-testing-runner libfprint-2 - --report-directory=_build/installed-tests-report/failed/ - --log-directory=_build/installed-tests-report/logs/ + --report-directory=_installed-tests-report/failed/ + --log-directory=_installed-tests-report/logs/ --parallel=0 artifacts: expose_as: 'GNOME Tests Runner logs' when: always paths: - - _build/meson-logs - - _build/installed-tests-report + - _build_dir/meson-logs + - _installed-tests-report expire_in: 1 week From af3dca90031b39d657b6fc1cc06f25498bbad292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 23 Aug 2023 01:04:33 +0200 Subject: [PATCH 09/79] tests/egis0570/capture.pcapng: Remove execution permission --- tests/egis0570/capture.pcapng | Bin 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 tests/egis0570/capture.pcapng diff --git a/tests/egis0570/capture.pcapng b/tests/egis0570/capture.pcapng old mode 100755 new mode 100644 From 206e92218c0209d70f156af08a17f9d945fa1b63 Mon Sep 17 00:00:00 2001 From: Vincent Huang Date: Tue, 4 Jul 2023 15:45:36 +0800 Subject: [PATCH 10/79] synaptics: fix enroll_identify problem after user reset database --- libfprint/drivers/synaptics/synaptics.c | 23 ++++++++++++++--------- libfprint/drivers/synaptics/synaptics.h | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/libfprint/drivers/synaptics/synaptics.c b/libfprint/drivers/synaptics/synaptics.c index f13b820b..99717c1e 100644 --- a/libfprint/drivers/synaptics/synaptics.c +++ b/libfprint/drivers/synaptics/synaptics.c @@ -106,7 +106,11 @@ cmd_receive_cb (FpiUsbTransfer *transfer, if (self->cmd_complete_on_removal) { - fpi_ssm_mark_completed (transfer->ssm); + if (self->delay_error) + fpi_ssm_mark_failed (transfer->ssm, + g_steal_pointer (&self->delay_error)); + else + fpi_ssm_mark_completed (transfer->ssm); return; } } @@ -641,18 +645,20 @@ verify (FpDevice *device) } static void -identify_complete_after_finger_removal (FpiDeviceSynaptics *self) +identify_complete_after_finger_removal (FpiDeviceSynaptics *self, GError *error) { FpDevice *device = FP_DEVICE (self); if (self->finger_on_sensor) { fp_dbg ("delaying identify report until after finger removal!"); + g_propagate_error (&self->delay_error, error); + self->cmd_complete_on_removal = TRUE; } else { - fpi_device_identify_complete (device, NULL); + fpi_device_identify_complete (device, error); } } @@ -702,19 +708,18 @@ identify_msg_cb (FpiDeviceSynaptics *self, fp_info ("Match error occurred"); fpi_device_identify_report (device, NULL, NULL, fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); - identify_complete_after_finger_removal (self); + identify_complete_after_finger_removal (self, NULL); } else if (resp->result == BMKT_FP_NO_MATCH) { fp_info ("Print didn't match"); fpi_device_identify_report (device, NULL, NULL, NULL); - identify_complete_after_finger_removal (self); + identify_complete_after_finger_removal (self, NULL); } - else if (resp->result == BMKT_FP_DATABASE_NO_RECORD_EXISTS) + else if (resp->result == BMKT_FP_DATABASE_NO_RECORD_EXISTS || resp->result == BMKT_FP_DATABASE_EMPTY) { fp_info ("Print is not in database"); - fpi_device_identify_complete (device, - fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); + identify_complete_after_finger_removal (self, fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); } else { @@ -750,7 +755,7 @@ identify_msg_cb (FpiDeviceSynaptics *self, else fpi_device_identify_report (device, NULL, print, NULL); - identify_complete_after_finger_removal (self); + identify_complete_after_finger_removal (self, NULL); } } } diff --git a/libfprint/drivers/synaptics/synaptics.h b/libfprint/drivers/synaptics/synaptics.h index 5fc0a192..6138db3f 100644 --- a/libfprint/drivers/synaptics/synaptics.h +++ b/libfprint/drivers/synaptics/synaptics.h @@ -127,4 +127,5 @@ struct _FpiDeviceSynaptics struct syna_enroll_resp_data enroll_resp_data; syna_state_t state; + GError *delay_error; }; From 5bff5bfea6fd28a63277d4132dbd086c84bcf2c2 Mon Sep 17 00:00:00 2001 From: Vincent Huang Date: Fri, 8 Sep 2023 12:47:53 +0800 Subject: [PATCH 11/79] synaptics: Add null check to prevent g_propagate_error assertion failure --- libfprint/drivers/synaptics/synaptics.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/synaptics/synaptics.c b/libfprint/drivers/synaptics/synaptics.c index 99717c1e..98ef199d 100644 --- a/libfprint/drivers/synaptics/synaptics.c +++ b/libfprint/drivers/synaptics/synaptics.c @@ -652,7 +652,8 @@ identify_complete_after_finger_removal (FpiDeviceSynaptics *self, GError *error) if (self->finger_on_sensor) { fp_dbg ("delaying identify report until after finger removal!"); - g_propagate_error (&self->delay_error, error); + if (error) + g_propagate_error (&self->delay_error, error); self->cmd_complete_on_removal = TRUE; } From eb01d7c97d3299f3577e8fc6d0a1e2043518a642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 27 Sep 2023 16:21:04 +0200 Subject: [PATCH 12/79] ci: Fix building flatpak using GNOME 42 runtime Use versioned ci template so we are sure what we're using --- .gitlab-ci.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 590eb203..acd0b6db 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ include: - project: 'freedesktop/ci-templates' ref: master file: '/templates/fedora.yml' - - remote: 'https://gitlab.gnome.org/GNOME/citemplates/-/raw/master/flatpak/flatpak_ci_initiative.yml' + - remote: 'https://gitlab.gnome.org/GNOME/citemplates/-/raw/21e7c107/flatpak/flatpak_ci_initiative.yml' default: # Auto-retry jobs in case of infra failures @@ -22,7 +22,6 @@ variables: FDO_DISTRIBUTION_VERSION: rawhide FDO_UPSTREAM_REPO: "libfprint/$CI_PROJECT_NAME" FEDORA_IMAGE: "$CI_REGISTRY/libfprint/$CI_PROJECT_NAME/fedora/$FDO_DISTRIBUTION_VERSION:$FDO_DISTRIBUTION_TAG" - BUNDLE: "org.freedesktop.libfprint.Demo.flatpak" LAST_ABI_BREAK: "056ea541ddc97f5806cffbd99a12dc87e4da3546" workflow: @@ -177,14 +176,18 @@ test_unsupported_list: - tests/hwdb-check-unsupported.py flatpak: - stage: flatpak - extends: .flatpak + #stage: flatpak + stage: image-build + extends: .flatpak@x86_64 # From https://gitlab.gnome.org/GNOME/gnome-runtime-images/container_registry - image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:42 + image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/x86_64/gnome:42 variables: MANIFEST_PATH: "demo/org.freedesktop.libfprint.Demo.json" FLATPAK_MODULE: "libfprint" APP_ID: "org.freedesktop.libfprint.Demo" + BUNDLE: "org.freedesktop.libfprint.Demo.flatpak" + # Build with any builder + tags: [] rules: - if: '$CI_PROJECT_PATH != "libfprint/libfprint"' when: never From 26d2c77c3df6f0a3367f27e9b802a094bef0f492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 27 Sep 2023 16:25:50 +0200 Subject: [PATCH 13/79] ci: Allow manual flatpak build in any fork and MR --- .gitlab-ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index acd0b6db..9ddc7260 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -176,8 +176,7 @@ test_unsupported_list: - tests/hwdb-check-unsupported.py flatpak: - #stage: flatpak - stage: image-build + stage: flatpak extends: .flatpak@x86_64 # From https://gitlab.gnome.org/GNOME/gnome-runtime-images/container_registry image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/x86_64/gnome:42 @@ -190,7 +189,8 @@ flatpak: tags: [] rules: - if: '$CI_PROJECT_PATH != "libfprint/libfprint"' - when: never + when: manual + allow_failure: true - if: '$CI_PIPELINE_SOURCE == "schedule"' when: never - if: '$CI_COMMIT_BRANCH == "master"' @@ -202,6 +202,9 @@ flatpak: - if: '$CI_COMMIT_BRANCH' when: manual allow_failure: true + - if: '$CI_MERGE_REQUEST_ID' + when: manual + allow_failure: true # CONTAINERS creation stage .container_fedora_build_base: From efba965b0cfd0b4bbf1608e604a5201edebb9168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 27 Sep 2023 17:18:42 +0200 Subject: [PATCH 14/79] ci: Build flatpak using gnome master Also bump dependencies versions --- .gitlab-ci.yml | 5 ++--- demo/org.freedesktop.libfprint.Demo.json | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9ddc7260..8bea6726 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ include: - project: 'freedesktop/ci-templates' ref: master file: '/templates/fedora.yml' - - remote: 'https://gitlab.gnome.org/GNOME/citemplates/-/raw/21e7c107/flatpak/flatpak_ci_initiative.yml' + - remote: 'https://gitlab.gnome.org/GNOME/citemplates/-/raw/master/flatpak/flatpak_ci_initiative.yml' default: # Auto-retry jobs in case of infra failures @@ -178,13 +178,12 @@ test_unsupported_list: flatpak: stage: flatpak extends: .flatpak@x86_64 - # From https://gitlab.gnome.org/GNOME/gnome-runtime-images/container_registry - image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/x86_64/gnome:42 variables: MANIFEST_PATH: "demo/org.freedesktop.libfprint.Demo.json" FLATPAK_MODULE: "libfprint" APP_ID: "org.freedesktop.libfprint.Demo" BUNDLE: "org.freedesktop.libfprint.Demo.flatpak" + RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo" # Build with any builder tags: [] rules: diff --git a/demo/org.freedesktop.libfprint.Demo.json b/demo/org.freedesktop.libfprint.Demo.json index 8fd52f56..55e6a278 100644 --- a/demo/org.freedesktop.libfprint.Demo.json +++ b/demo/org.freedesktop.libfprint.Demo.json @@ -1,7 +1,7 @@ { "app-id": "org.freedesktop.libfprint.Demo", "runtime": "org.gnome.Platform", - "runtime-version": "42", + "runtime-version": "master", "sdk": "org.gnome.Sdk", "command": "gtk-libfprint-test", "finish-args": [ @@ -38,24 +38,24 @@ { "name": "libgusb", "buildsystem": "meson", - "config-opts": [ "-Dtests=false", "-Dvapi=false", "-Ddocs=false", "-Dintrospection=false" ], + "config-opts": [ "-Dtests=false", "-Dvapi=false", "-Ddocs=false" ], "sources": [ { "type": "archive", - "url": "https://github.com/hughsie/libgusb/archive/0.3.0.tar.gz", - "sha256": "b36310f8405d5fd68f6caf4a829f7ab4c627b38fd3d02a139d411fce0f3a49f1" + "url": "https://github.com/hughsie/libgusb/releases/download/0.4.6/libgusb-0.4.6.tar.xz", + "sha256": "1b0422bdcd72183272ac42eec9398c5a0bc48a02f618fa3242c468cbbd003049" } ] }, { "name": "gudev", "buildsystem": "meson", - "config-opts": [ "-Dtests=disabled", "-Dintrospection=disabled" ], + "config-opts": [ "-Dtests=disabled", "-Dintrospection=disabled", "-Dvapi=disabled" ], "sources": [ { "type": "archive", - "url": "https://download.gnome.org/sources/libgudev/236/libgudev-236.tar.xz", - "sha256": "e50369d06d594bae615eb7aeb787de304ebaad07a26d1043cef8e9c7ab7c9524" + "url": "https://download.gnome.org/sources/libgudev/238/libgudev-238.tar.xz", + "sha256": "61266ab1afc9d73dbc60a8b2af73e99d2fdff47d99544d085760e4fa667b5dd1" } ] }, From 0eae0e8cc0ebc23dabfe2048345e26c46460f993 Mon Sep 17 00:00:00 2001 From: swbgdx Date: Thu, 21 Sep 2023 18:11:23 +0800 Subject: [PATCH 15/79] goodixmoc: Add PID 0x6582 --- data/autosuspend.hwdb | 1 + libfprint/drivers/goodixmoc/goodix.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 603c5c1f..c9746756 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -186,6 +186,7 @@ usb:v27C6p63AC* usb:v27C6p63BC* usb:v27C6p63CC* usb:v27C6p6496* +usb:v27C6p6582* usb:v27C6p6584* usb:v27C6p658C* usb:v27C6p6592* diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index bacb4845..5a3fface 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -1374,6 +1374,7 @@ gx_fp_probe (FpDevice *device) case 0x63AC: case 0x63BC: case 0x63CC: + case 0x6582: case 0x6A94: case 0x659A: self->max_enroll_stage = 12; @@ -1623,6 +1624,7 @@ static const FpIdEntry id_table[] = { { .vid = 0x27c6, .pid = 0x63BC, }, { .vid = 0x27c6, .pid = 0x63CC, }, { .vid = 0x27c6, .pid = 0x6496, }, + { .vid = 0x27c6, .pid = 0x6582, }, { .vid = 0x27c6, .pid = 0x6584, }, { .vid = 0x27c6, .pid = 0x658C, }, { .vid = 0x27c6, .pid = 0x6592, }, From 2414dbdbd46e762b944c2dc421c5fe820e8d4e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 27 Sep 2023 19:37:15 +0200 Subject: [PATCH 16/79] libfprint/fprint-list-udev-hwdb: Update unsupported devices from wiki --- data/autosuspend.hwdb | 10 ++++++++++ libfprint/fprint-list-udev-hwdb.c | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index c9746756..eb703a79 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -285,6 +285,7 @@ usb:v138Ap0091* ID_PERSIST=0 # Known unsupported devices +usb:v047Dp00F2* usb:v04E8p730B* usb:v04F3p036B* usb:v04F3p0C00* @@ -292,9 +293,12 @@ usb:v04F3p0C4C* usb:v04F3p0C57* usb:v04F3p0C5E* usb:v04F3p0C5A* +usb:v04F3p0C6C* usb:v04F3p0C70* usb:v04F3p0C72* +usb:v04F3p0C77* usb:v04F3p2706* +usb:v04F3p3032* usb:v04F3p3057* usb:v04F3p3104* usb:v04F3p310D* @@ -343,12 +347,16 @@ usb:v138Ap0094* usb:v138Ap0097* usb:v138Ap009D* usb:v138Ap00AB* +usb:v138Ap00A6* usb:v147Ep1002* usb:v1491p0088* usb:v16D1p1027* usb:v1C7Ap0300* usb:v1C7Ap0575* usb:v1C7Ap0576* +usb:v1C7Ap0577* +usb:v1C7Ap0582* +usb:v1C7Ap05A1* usb:v27C6p5042* usb:v27C6p5110* usb:v27C6p5117* @@ -376,6 +384,8 @@ usb:v27C6p55B4* usb:v27C6p5740* usb:v27C6p5E0A* usb:v27C6p581A* +usb:v27C6p589A* +usb:v27C6p6382* usb:v2808p9338* usb:v2808p93A9* usb:v298Dp2020* diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index cf1c4ecf..7c41050b 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -29,6 +29,7 @@ static const FpIdEntry whitelist_id_table[] = { * You can generate this list from the wiki page using e.g.: * gio cat https://gitlab.freedesktop.org/libfprint/wiki/-/wikis/Unsupported-Devices.md | sed -n 's!|.*\([0-9a-fA-F]\{4\}\):\([0-9a-fA-F]\{4\}\).*|.*! { .vid = 0x\1, .pid = 0x\2 },!p' */ + { .vid = 0x047d, .pid = 0x00f2 }, { .vid = 0x04e8, .pid = 0x730b }, { .vid = 0x04f3, .pid = 0x036b }, { .vid = 0x04f3, .pid = 0x0c00 }, @@ -36,9 +37,12 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x04f3, .pid = 0x0c57 }, { .vid = 0x04f3, .pid = 0x0c5e }, { .vid = 0x04f3, .pid = 0x0c5a }, + { .vid = 0x04f3, .pid = 0x0c6c }, { .vid = 0x04f3, .pid = 0x0c70 }, { .vid = 0x04f3, .pid = 0x0c72 }, + { .vid = 0x04f3, .pid = 0x0c77 }, { .vid = 0x04f3, .pid = 0x2706 }, + { .vid = 0x04f3, .pid = 0x3032 }, { .vid = 0x04f3, .pid = 0x3057 }, { .vid = 0x04f3, .pid = 0x3104 }, { .vid = 0x04f3, .pid = 0x310d }, @@ -87,12 +91,16 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x138a, .pid = 0x0097 }, { .vid = 0x138a, .pid = 0x009d }, { .vid = 0x138a, .pid = 0x00ab }, + { .vid = 0x138a, .pid = 0x00a6 }, { .vid = 0x147e, .pid = 0x1002 }, { .vid = 0x1491, .pid = 0x0088 }, { .vid = 0x16d1, .pid = 0x1027 }, { .vid = 0x1c7a, .pid = 0x0300 }, { .vid = 0x1c7a, .pid = 0x0575 }, { .vid = 0x1c7a, .pid = 0x0576 }, + { .vid = 0x1c7a, .pid = 0x0577 }, + { .vid = 0x1c7a, .pid = 0x0582 }, + { .vid = 0x1c7a, .pid = 0x05a1 }, { .vid = 0x27c6, .pid = 0x5042 }, { .vid = 0x27c6, .pid = 0x5110 }, { .vid = 0x27c6, .pid = 0x5117 }, @@ -120,6 +128,8 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x27c6, .pid = 0x5740 }, { .vid = 0x27c6, .pid = 0x5e0a }, { .vid = 0x27c6, .pid = 0x581a }, + { .vid = 0x27c6, .pid = 0x589a }, + { .vid = 0x27c6, .pid = 0x6382 }, { .vid = 0x2808, .pid = 0x9338 }, { .vid = 0x2808, .pid = 0x93a9 }, { .vid = 0x298d, .pid = 0x2020 }, From 3ebd2c3f9745b80d02e5724c5737ff023be04fae Mon Sep 17 00:00:00 2001 From: Aris Lin Date: Thu, 19 Oct 2023 14:25:09 +0800 Subject: [PATCH 17/79] synaptics: Add new PID 0x0173 --- data/autosuspend.hwdb | 1 + libfprint/drivers/synaptics/synaptics.c | 1 + 2 files changed, 2 insertions(+) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index eb703a79..6f5fcf20 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -217,6 +217,7 @@ usb:v06CBp0129* usb:v06CBp0168* usb:v06CBp015F* usb:v06CBp0104* +usb:v06CBp0173* ID_AUTOSUSPEND=1 ID_PERSIST=0 diff --git a/libfprint/drivers/synaptics/synaptics.c b/libfprint/drivers/synaptics/synaptics.c index 98ef199d..c178abbb 100644 --- a/libfprint/drivers/synaptics/synaptics.c +++ b/libfprint/drivers/synaptics/synaptics.c @@ -45,6 +45,7 @@ static const FpIdEntry id_table[] = { { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0168, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x015F, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0104, }, + { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0173, }, { .vid = 0, .pid = 0, .driver_data = 0 }, /* terminating entry */ }; From 427139f347c576e08d6656142ba39d06c7be2b15 Mon Sep 17 00:00:00 2001 From: Aris Lin Date: Tue, 24 Oct 2023 14:56:15 +0800 Subject: [PATCH 18/79] synaptics: Add new PID 0x0106 --- data/autosuspend.hwdb | 1 + libfprint/drivers/synaptics/synaptics.c | 1 + 2 files changed, 2 insertions(+) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 6f5fcf20..35abc907 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -218,6 +218,7 @@ usb:v06CBp0168* usb:v06CBp015F* usb:v06CBp0104* usb:v06CBp0173* +usb:v06CBp0106* ID_AUTOSUSPEND=1 ID_PERSIST=0 diff --git a/libfprint/drivers/synaptics/synaptics.c b/libfprint/drivers/synaptics/synaptics.c index c178abbb..55e4d44c 100644 --- a/libfprint/drivers/synaptics/synaptics.c +++ b/libfprint/drivers/synaptics/synaptics.c @@ -46,6 +46,7 @@ static const FpIdEntry id_table[] = { { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x015F, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0104, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0173, }, + { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0106, }, { .vid = 0, .pid = 0, .driver_data = 0 }, /* terminating entry */ }; From 79be91831c564f6c8b1af4c0c52c330632923acb Mon Sep 17 00:00:00 2001 From: huan_huang Date: Fri, 4 Aug 2023 10:21:17 +0800 Subject: [PATCH 19/79] drivers: add realtek rts5813 driver --- data/autosuspend.hwdb | 5 + libfprint/drivers/realtek/realtek.c | 1220 +++++++++++++++++++++++++++ libfprint/drivers/realtek/realtek.h | 220 +++++ libfprint/meson.build | 2 + meson.build | 1 + tests/meson.build | 1 + tests/realtek/custom.pcapng | Bin 0 -> 29128 bytes tests/realtek/custom.py | 110 +++ tests/realtek/device | 240 ++++++ 9 files changed, 1799 insertions(+) create mode 100644 libfprint/drivers/realtek/realtek.c create mode 100644 libfprint/drivers/realtek/realtek.h create mode 100644 tests/realtek/custom.pcapng create mode 100755 tests/realtek/custom.py create mode 100644 tests/realtek/device diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 35abc907..2b4dc706 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -202,6 +202,11 @@ usb:v298Dp1010* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver realtek +usb:v0BDAp5813* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver synaptics usb:v06CBp00BD* usb:v06CBp00DF* diff --git a/libfprint/drivers/realtek/realtek.c b/libfprint/drivers/realtek/realtek.c new file mode 100644 index 00000000..87b8c805 --- /dev/null +++ b/libfprint/drivers/realtek/realtek.c @@ -0,0 +1,1220 @@ +/* + * Copyright (C) 2022-2023 Realtek Corp. + * + * 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 + */ + +#define FP_COMPONENT "realtek" + +#include "drivers_api.h" + +#include "fpi-byte-reader.h" + +#include "realtek.h" + +G_DEFINE_TYPE (FpiDeviceRealtek, fpi_device_realtek, FP_TYPE_DEVICE) + +static const FpIdEntry id_table[] = { + { .vid = 0x0bda, .pid = 0x5813, }, + { .vid = 0, .pid = 0, .driver_data = 0 }, /* terminating entry */ +}; + +static gboolean +parse_print_data (GVariant *data, + guint8 *finger, + const guint8 **user_id, + gsize *user_id_len) +{ + g_autoptr(GVariant) user_id_var = NULL; + + g_return_val_if_fail (data, FALSE); + g_return_val_if_fail (finger, FALSE); + g_return_val_if_fail (user_id, FALSE); + g_return_val_if_fail (user_id_len, FALSE); + + *user_id = NULL; + *user_id_len = 0; + *finger = 0; + + if (!g_variant_check_format_string (data, "(y@ay)", FALSE)) + return FALSE; + + g_variant_get (data, + "(y@ay)", + finger, + &user_id_var); + + *user_id = g_variant_get_fixed_array (user_id_var, user_id_len, 1); + + if (*user_id_len <= 0 || *user_id_len > DEFAULT_UID_LEN) + return FALSE; + + if (*user_id[0] == '\0' || *user_id[0] == ' ') + return FALSE; + + if (*finger != SUB_FINGER_01) + return FALSE; + + return TRUE; +} + +static void +fp_cmd_ssm_done_data_free (CommandData *data) +{ + g_free (data); +} + +/* data callbacks */ + +static void +fp_task_ssm_generic_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fp_finish_capture_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + gint capture_status = buffer_in[0]; + if (capture_status == 0) + { + fpi_device_report_finger_status_changes (FP_DEVICE (self), + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NEEDED); + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_ssm_jump_to_state (self->task_ssm, + fpi_ssm_get_cur_state (self->task_ssm)); + } +} + +static void +fp_accept_sample_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + fpi_device_report_finger_status_changes (FP_DEVICE (self), + FP_FINGER_STATUS_NONE, + FP_FINGER_STATUS_PRESENT); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + gint in_status = buffer_in[0]; + + if (self->fp_purpose != FP_RTK_PURPOSE_ENROLL) + { + /* verify or identify purpose process */ + fpi_ssm_next_state (self->task_ssm); + return; + } + else + { + /* enroll purpose process */ + if (in_status == FP_RTK_CMD_ERR) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Command error!")); + return; + } + + if (self->enroll_stage < self->max_enroll_stage) + { + if (in_status == FP_RTK_SUCCESS) + { + self->enroll_stage++; + fpi_device_enroll_progress (FP_DEVICE (self), self->enroll_stage, NULL, NULL); + fpi_ssm_jump_to_state (self->task_ssm, FP_RTK_ENROLL_CAPTURE); + } + else if (in_status == FP_RTK_MATCH_FAIL) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "InStatus invalid!")); + } + else + { + fpi_device_enroll_progress (FP_DEVICE (self), + self->enroll_stage, + NULL, + fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); + + fpi_ssm_jump_to_state (self->task_ssm, FP_RTK_ENROLL_CAPTURE); + } + return; + } + fpi_ssm_next_state (self->task_ssm); + } +} + +static FpPrint * +fp_print_from_data (FpiDeviceRealtek *self, uint8_t *buffer) +{ + FpPrint *print; + GVariant *data; + GVariant *uid; + guint finger; + gsize userid_len; + g_autofree gchar *userid = NULL; + + userid = g_strndup ((gchar *) buffer + 1, DEFAULT_UID_LEN); + finger = *(buffer); + + print = fp_print_new (FP_DEVICE (self)); + userid_len = MIN (DEFAULT_UID_LEN, strlen (userid)); + + uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + userid, + userid_len, + 1); + + data = g_variant_new ("(y@ay)", + finger, + uid); + + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + g_object_set (print, "fpi-data", data, NULL); + g_object_set (print, "description", userid, NULL); + fpi_print_fill_from_user_id (print, userid); + + return print; +} + +static void +fp_identify_feature_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + FpDevice *device = FP_DEVICE (self); + FpPrint *match = NULL; + FpPrint *print = NULL; + FpiDeviceAction current_action; + + g_autoptr(GPtrArray) templates = NULL; + gboolean found = FALSE; + + current_action = fpi_device_get_current_action (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + gint in_status = buffer_in[0]; + if (in_status == FP_RTK_CMD_ERR) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Command error!")); + return; + } + + if (in_status >= FP_RTK_TOO_HIGH && in_status <= FP_RTK_MERGE_FAILURE) + { + GError *retry_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); + retry_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); + fpi_ssm_mark_failed (self->task_ssm, retry_error); + return; + } + + if (in_status == FP_RTK_SUCCESS) + { + match = fp_print_from_data (self, buffer_in + 1); + + if (current_action == FPI_DEVICE_ACTION_VERIFY) + { + templates = g_ptr_array_sized_new (1); + fpi_device_get_verify_data (device, &print); + g_ptr_array_add (templates, print); + } + else + { + fpi_device_get_identify_data (device, &templates); + g_ptr_array_ref (templates); + } + + for (gint cnt = 0; cnt < templates->len; cnt++) + { + print = g_ptr_array_index (templates, cnt); + + if (fp_print_equal (print, match)) + { + found = TRUE; + break; + } + } + + if (found) + { + if (current_action == FPI_DEVICE_ACTION_VERIFY) + { + fpi_device_verify_report (device, FPI_MATCH_SUCCESS, match, error); + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_device_identify_report (device, print, match, error); + fpi_ssm_mark_completed (self->task_ssm); + } + return; + } + } + + if (!found) + { + if (current_action == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, error); + else + fpi_device_identify_report (device, NULL, NULL, error); + + fpi_ssm_jump_to_state (self->task_ssm, FP_RTK_VERIFY_NUM_STATES); + } +} + +static void +fp_get_delete_pos_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + FpPrint *print = NULL; + + g_autoptr(GVariant) data = NULL; + gsize user_id_len = 0; + const guint8 *user_id; + guint8 finger; + gboolean found = FALSE; + gchar temp_userid[DEFAULT_UID_LEN + 1] = {0}; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fpi_device_get_delete_data (FP_DEVICE (self), &print); + g_object_get (print, "fpi-data", &data, NULL); + + if (!parse_print_data (data, &finger, &user_id, &user_id_len)) + { + fpi_device_delete_complete (FP_DEVICE (self), + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + + for (gint i = 0; i < self->template_num; i++) + { + if (buffer_in[i * TEMPLATE_LEN] != 0) + { + memcpy (temp_userid, buffer_in + i * TEMPLATE_LEN + UID_OFFSET, DEFAULT_UID_LEN); + if (g_strcmp0 (fp_print_get_description (print), (const char *) temp_userid) == 0) + { + self->pos_index = i; + found = TRUE; + break; + } + } + } + + if (!found) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Get template position failed!")); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fp_get_enroll_num_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + self->template_num = buffer_in[1]; + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fp_get_template_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + gboolean found = FALSE; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + for (gint i = 0; i < self->template_num; i++) + { + if (buffer_in[i * TEMPLATE_LEN] == 0) + { + self->pos_index = i; + found = TRUE; + break; + } + } + + if (!found) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "No free template was found!")); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fp_check_duplicate_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + gint in_status = buffer_in[0]; + if (in_status == FP_RTK_CMD_ERR) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Command error!")); + return; + } + + if (in_status == FP_RTK_SUCCESS) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Current fingerprint is duplicate!")); + } + else if (in_status == FP_RTK_MATCH_FAIL) + { + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "InStatus invalid!")); + } +} + +static void +fp_list_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + gboolean found = FALSE; + + g_autoptr(GPtrArray) list_result = NULL; + + if (error) + { + fpi_device_list_complete (FP_DEVICE (self), NULL, error); + return; + } + + list_result = g_ptr_array_new_with_free_func (g_object_unref); + + for (gint i = 0; i < self->template_num; i++) + { + if (buffer_in[i * TEMPLATE_LEN] != 0) + { + FpPrint *print = NULL; + print = fp_print_from_data (self, buffer_in + i * TEMPLATE_LEN + SUBFACTOR_OFFSET); + g_ptr_array_add (list_result, g_object_ref_sink (print)); + found = TRUE; + } + } + + if (!found) + { + fpi_device_list_complete (FP_DEVICE (self), + g_steal_pointer (&list_result), + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_FULL, + "Database is empty")); + return; + } + + fp_info ("Query templates complete!"); + fpi_device_list_complete (FP_DEVICE (self), + g_steal_pointer (&list_result), + NULL); +} + +static void +fp_clear_storage_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + FpDevice *device = FP_DEVICE (self); + + if (error) + { + fpi_device_clear_storage_complete (device, error); + return; + } + + fp_info ("Successfully cleared storage"); + fpi_device_clear_storage_complete (device, NULL); +} + + +static gint +parse_status (guint8 *buffer, gint status_type) +{ + switch (status_type) + { + case FP_RTK_MSG_PLAINTEXT_NO_STATUS: + return 0; + break; + + case FP_RTK_MSG_PLAINTEXT: + return buffer[0]; + break; + + default: + return 1; + break; + } +} + +static void +fp_cmd_receive_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer user_data, + GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + CommandData *data = user_data; + gint ssm_state = 0; + gint status_flag = 1; + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + if (data == NULL) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + ssm_state = fpi_ssm_get_cur_state (transfer->ssm); + + /* skip zero length package */ + if (transfer->actual_length == 0) + { + fpi_ssm_jump_to_state (transfer->ssm, ssm_state); + return; + } + + /* get data */ + if (ssm_state == FP_RTK_CMD_TRANS_DATA) + { + g_autofree guchar *read_buf = NULL; + + read_buf = g_malloc0 (sizeof (guchar) * (self->trans_data_len)); + memcpy (read_buf, transfer->buffer, self->trans_data_len); + self->read_data = g_steal_pointer (&read_buf); + + fpi_ssm_next_state (transfer->ssm); + return; + } + + /* get status */ + status_flag = parse_status (transfer->buffer, self->message_type); + if (status_flag != 0) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Status check failed")); + return; + } + + if (data->callback) + data->callback (self, self->read_data, NULL); + + if (self->read_data) + g_clear_pointer (&self->read_data, g_free); + + fpi_ssm_mark_completed (transfer->ssm); +} + +static void +fp_cmd_run_state (FpiSsm *ssm, FpDevice *dev) +{ + FpiUsbTransfer *transfer = NULL; + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_CMD_SEND: + if (self->cmd_transfer) + { + self->cmd_transfer->ssm = ssm; + fpi_usb_transfer_submit (g_steal_pointer (&self->cmd_transfer), + CMD_TIMEOUT, + NULL, + fpi_ssm_usb_transfer_cb, + NULL); + } + else + { + fpi_ssm_next_state (ssm); + } + break; + + case FP_RTK_CMD_TRANS_DATA: + if (self->cmd_type == FP_RTK_CMD_ONLY) + { + fpi_ssm_jump_to_state (ssm, FP_RTK_CMD_GET_STATUS); + break; + } + + if (self->cmd_type == FP_RTK_CMD_WRITE) + { + if (self->data_transfer) + { + self->data_transfer->ssm = ssm; + fpi_usb_transfer_submit (g_steal_pointer (&self->data_transfer), + DATA_TIMEOUT, + NULL, + fpi_ssm_usb_transfer_cb, + NULL); + } + else + { + fpi_ssm_next_state (ssm); + } + } + else /* CMD_READ */ + { + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, EP_IN, EP_IN_MAX_BUF_SIZE); + + fpi_usb_transfer_submit (transfer, + self->cmd_cancellable ? 0 : DATA_TIMEOUT, + self->cmd_cancellable ? fpi_device_get_cancellable (dev) : NULL, + fp_cmd_receive_cb, + fpi_ssm_get_data (ssm)); + } + break; + + case FP_RTK_CMD_GET_STATUS: + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, EP_IN, EP_IN_MAX_BUF_SIZE); + fpi_usb_transfer_submit (transfer, + STATUS_TIMEOUT, + NULL, + fp_cmd_receive_cb, + fpi_ssm_get_data (ssm)); + break; + } +} + +static void +fp_cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + CommandData *data = fpi_ssm_get_data (ssm); + + self->cmd_ssm = NULL; + + if (error) + { + if (data->callback) + data->callback (self, NULL, error); + else + g_error_free (error); + } +} + +static FpiUsbTransfer * +prepare_transfer (FpDevice *dev, + guint8 *data, + gsize data_len, + GDestroyNotify free_func) +{ + g_autoptr(FpiUsbTransfer) transfer = NULL; + + g_return_val_if_fail (data || data_len == 0, NULL); + + transfer = fpi_usb_transfer_new (dev); + + fpi_usb_transfer_fill_bulk_full (transfer, + EP_OUT, + data, + data_len, + free_func); + + return g_steal_pointer (&transfer); +} + +static void +realtek_sensor_cmd (FpiDeviceRealtek *self, + guint8 *cmd, + guint8 *trans_data, + FpRtkMsgType message_type, + gboolean bwait_data_delay, + SynCmdMsgCallback callback) +{ + g_autoptr(FpiUsbTransfer) cmd_transfer = NULL; + g_autoptr(FpiUsbTransfer) data_transfer = NULL; + CommandData *data = g_new0 (CommandData, 1); + + self->cmd_type = GET_CMD_TYPE (cmd[0]); + self->message_type = message_type; + self->trans_data_len = GET_TRANS_DATA_LEN (cmd[11], cmd[10]); + self->cmd_cancellable = bwait_data_delay; + + cmd_transfer = prepare_transfer (FP_DEVICE (self), cmd, FP_RTK_CMD_TOTAL_LEN, NULL); + self->cmd_transfer = g_steal_pointer (&cmd_transfer); + + if ((self->cmd_type == FP_RTK_CMD_WRITE) && trans_data) + { + data_transfer = prepare_transfer (FP_DEVICE (self), trans_data, self->trans_data_len, g_free); + self->data_transfer = g_steal_pointer (&data_transfer); + } + + self->cmd_ssm = fpi_ssm_new (FP_DEVICE (self), + fp_cmd_run_state, + FP_RTK_CMD_NUM_STATES); + + data->callback = callback; + fpi_ssm_set_data (self->cmd_ssm, data, (GDestroyNotify) fp_cmd_ssm_done_data_free); + + fpi_ssm_start (self->cmd_ssm, fp_cmd_ssm_done); +} + +static void +fp_verify_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + + fp_info ("Verify complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + if (error && error->domain == FP_DEVICE_RETRY) + { + if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (dev, FPI_MATCH_ERROR, NULL, g_steal_pointer (&error)); + else + fpi_device_identify_report (dev, NULL, NULL, g_steal_pointer (&error)); + } + + if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_complete (dev, error); + else + fpi_device_identify_complete (dev, error); + + self->task_ssm = NULL; +} + +static void +fp_enroll_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + FpPrint *print = NULL; + + fp_info ("Enrollment complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + if (error) + { + fpi_device_enroll_complete (dev, NULL, error); + self->task_ssm = NULL; + return; + } + + fpi_device_get_enroll_data (FP_DEVICE (self), &print); + fpi_device_enroll_complete (FP_DEVICE (self), g_object_ref (print), NULL); + self->task_ssm = NULL; +} + +static void +fp_init_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + + fp_info ("Init complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + fpi_device_open_complete (dev, error); + self->task_ssm = NULL; +} + +static void +fp_delete_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + + fp_info ("Delete print complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + fpi_device_delete_complete (dev, error); + self->task_ssm = NULL; +} + +static void +fp_verify_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_VERIFY_CAPTURE: + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + + cmd_buf = (guint8 *) &co_start_capture; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_task_ssm_generic_cb); + break; + + case FP_RTK_VERIFY_FINISH_CAPTURE: + cmd_buf = (guint8 *) &co_finish_capture; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_finish_capture_cb); + break; + + case FP_RTK_VERIFY_ACCEPT_SAMPLE: + co_accept_sample.param[0] = self->fp_purpose; + cmd_buf = (guint8 *) &co_accept_sample; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT_NO_STATUS, 1, fp_accept_sample_cb); + break; + + case FP_RTK_VERIFY_INDENTIFY_FEATURE: + cmd_buf = (guint8 *) &tls_identify_feature; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT_NO_STATUS, 0, fp_identify_feature_cb); + break; + + case FP_RTK_VERIFY_UPDATE_TEMPLATE: + cmd_buf = (guint8 *) &co_update_template; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + } +} + +static void +fp_enroll_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + FpPrint *print = NULL; + guint8 *cmd_buf = NULL; + + g_autofree gchar *user_id = NULL; + g_autofree guint8 *payload = NULL; + + GVariant *uid = NULL; + GVariant *data = NULL; + gsize user_id_len; + guint finger; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_ENROLL_GET_TEMPLATE: + g_assert (self->template_num > 0); + + co_get_template.data_len[0] = GET_LEN_L (TEMPLATE_LEN * self->template_num); + co_get_template.data_len[1] = GET_LEN_H (TEMPLATE_LEN * self->template_num); + + cmd_buf = (guint8 *) &co_get_template; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_get_template_cb); + break; + + case FP_RTK_ENROLL_BEGIN_POS: + tls_enroll_begin.param[0] = self->pos_index; + cmd_buf = (guint8 *) &tls_enroll_begin; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + + case FP_RTK_ENROLL_CAPTURE: + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + + cmd_buf = (guint8 *) &co_start_capture; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_task_ssm_generic_cb); + break; + + case FP_RTK_ENROLL_FINISH_CAPTURE: + cmd_buf = (guint8 *) &co_finish_capture; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_finish_capture_cb); + break; + + case FP_RTK_ENROLL_ACCEPT_SAMPLE: + co_accept_sample.param[0] = self->fp_purpose; + cmd_buf = (guint8 *) &co_accept_sample; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT_NO_STATUS, 1, fp_accept_sample_cb); + break; + + case FP_RTK_ENROLL_CHECK_DUPLICATE: + cmd_buf = (guint8 *) &co_check_duplicate; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT_NO_STATUS, 1, fp_check_duplicate_cb); + break; + + case FP_RTK_ENROLL_COMMIT: + guint8 *trans_id = NULL; + + fpi_device_get_enroll_data (device, &print); + user_id = fpi_print_generate_user_id (print); + user_id_len = strlen (user_id); + user_id_len = MIN (DEFAULT_UID_LEN, user_id_len); + + payload = g_malloc0 (UID_PAYLOAD_LEN); + memcpy (payload, user_id, user_id_len); + + trans_id = g_steal_pointer (&payload); + + finger = SUB_FINGER_01; + uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + user_id, + user_id_len, + 1); + data = g_variant_new ("(y@ay)", + finger, + uid); + + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + g_object_set (print, "fpi-data", data, NULL); + g_object_set (print, "description", user_id, NULL); + + g_debug ("user_id: %s, finger: 0x%x", user_id, finger); + + tls_enroll_commit.param[0] = SUB_FINGER_01; + cmd_buf = (guint8 *) &tls_enroll_commit; + realtek_sensor_cmd (self, cmd_buf, trans_id, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + } +} + +static void +fp_init_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_INIT_SELECT_OS: + co_select_system.param[0] = 0x01; + cmd_buf = (guint8 *) &co_select_system; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + + case FP_RTK_INIT_GET_ENROLL_NUM: + cmd_buf = (guint8 *) &co_get_enroll_num; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_get_enroll_num_cb); + break; + } +} + +static void +fp_delete_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_DELETE_GET_POS: + g_assert (self->template_num > 0); + + co_get_template.data_len[0] = GET_LEN_L (TEMPLATE_LEN * self->template_num); + co_get_template.data_len[1] = GET_LEN_H (TEMPLATE_LEN * self->template_num); + + cmd_buf = (guint8 *) &co_get_template; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_get_delete_pos_cb); + break; + + case FP_RTK_DELETE_PRINT: + co_delete_record.param[0] = self->pos_index; + cmd_buf = (guint8 *) &co_delete_record; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + } +} + + +static void +identify_verify (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + FpiDeviceAction current_action; + + G_DEBUG_HERE (); + current_action = fpi_device_get_current_action (device); + + g_assert (current_action == FPI_DEVICE_ACTION_VERIFY || + current_action == FPI_DEVICE_ACTION_IDENTIFY); + + if (current_action == FPI_DEVICE_ACTION_IDENTIFY) + self->fp_purpose = FP_RTK_PURPOSE_IDENTIFY; + else + self->fp_purpose = FP_RTK_PURPOSE_VERIFY; + + g_assert (!self->task_ssm); + + self->task_ssm = fpi_ssm_new_full (device, + fp_verify_sm_run_state, + FP_RTK_VERIFY_NUM_STATES, + FP_RTK_VERIFY_NUM_STATES, + "Verify & Identify"); + + fpi_ssm_start (self->task_ssm, fp_verify_ssm_done); +} + +static void +enroll (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + + G_DEBUG_HERE (); + self->enroll_stage = 0; + self->fp_purpose = FP_RTK_PURPOSE_ENROLL; + + g_assert (!self->task_ssm); + + self->task_ssm = fpi_ssm_new_full (device, + fp_enroll_sm_run_state, + FP_RTK_ENROLL_NUM_STATES, + FP_RTK_ENROLL_NUM_STATES, + "Enroll"); + + fpi_ssm_start (self->task_ssm, fp_enroll_ssm_done); +} + +static void +dev_probe (FpDevice *device) +{ + GUsbDevice *usb_dev; + GError *error = NULL; + g_autofree gchar *product = NULL; + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + + G_DEBUG_HERE (); + /* Claim usb interface */ + usb_dev = fpi_device_get_usb_device (device); + if (!g_usb_device_open (usb_dev, &error)) + { + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (!g_usb_device_reset (usb_dev, &error)) + { + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (!g_usb_device_claim_interface (usb_dev, 0, 0, &error)) + { + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + product = g_usb_device_get_string_descriptor (usb_dev, + g_usb_device_get_product_index (usb_dev), + &error); + + if (product) + fp_dbg ("Device name: %s", product); + + self->max_enroll_stage = MAX_ENROLL_SAMPLES; + fpi_device_set_nr_enroll_stages (device, self->max_enroll_stage); + + g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)), 0, 0, NULL); + g_usb_device_close (usb_dev, NULL); + + fpi_device_probe_complete (device, NULL, product, error); +} + +static void +dev_init (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + GError *error = NULL; + + G_DEBUG_HERE (); + if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) + { + fpi_device_open_complete (FP_DEVICE (self), error); + return; + } + + /* Claim usb interface */ + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error)) + { + fpi_device_open_complete (FP_DEVICE (self), error); + return; + } + + g_assert (!self->task_ssm); + + self->task_ssm = fpi_ssm_new_full (device, + fp_init_sm_run_state, + FP_RTK_INIT_NUM_STATES, + FP_RTK_INIT_NUM_STATES, + "Init"); + + fpi_ssm_start (self->task_ssm, fp_init_ssm_done); +} + +static void +dev_exit (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + + g_autoptr(GError) release_error = NULL; + + G_DEBUG_HERE (); + + g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (self)), 0, 0, &release_error); + + fpi_device_close_complete (device, release_error); +} + +static void +delete_print (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + + G_DEBUG_HERE (); + + g_assert (!self->task_ssm); + + self->task_ssm = fpi_ssm_new_full (device, + fp_delete_sm_run_state, + FP_RTK_DELETE_NUM_STATES, + FP_RTK_DELETE_NUM_STATES, + "Delete print"); + + fpi_ssm_start (self->task_ssm, fp_delete_ssm_done); +} + +static void +clear_storage (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + G_DEBUG_HERE (); + co_delete_record.param[0] = 0xff; + cmd_buf = (guint8 *) &co_delete_record; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_clear_storage_cb); +} + +static void +list_print (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + G_DEBUG_HERE (); + g_assert (self->template_num > 0); + + co_get_template.data_len[0] = GET_LEN_L (TEMPLATE_LEN * self->template_num); + co_get_template.data_len[1] = GET_LEN_H (TEMPLATE_LEN * self->template_num); + + cmd_buf = (guint8 *) &co_get_template; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_list_cb); +} + +static void +fpi_device_realtek_init (FpiDeviceRealtek *self) +{ +} + +static void +fpi_device_realtek_class_init (FpiDeviceRealtekClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = "Realtek MOC Fingerprint Sensor"; + + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + dev_class->id_table = id_table; + dev_class->nr_enroll_stages = MAX_ENROLL_SAMPLES; + dev_class->temp_hot_seconds = -1; + + dev_class->open = dev_init; + dev_class->close = dev_exit; + dev_class->probe = dev_probe; + dev_class->verify = identify_verify; + dev_class->identify = identify_verify; + dev_class->enroll = enroll; + dev_class->delete = delete_print; + dev_class->clear_storage = clear_storage; + dev_class->list = list_print; + + fpi_device_class_auto_initialize_features (dev_class); +} diff --git a/libfprint/drivers/realtek/realtek.h b/libfprint/drivers/realtek/realtek.h new file mode 100644 index 00000000..803922fe --- /dev/null +++ b/libfprint/drivers/realtek/realtek.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2022-2023 Realtek Corp. + * + * 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 + */ + +#pragma once + +#include "fpi-device.h" +#include "fpi-ssm.h" + +#include +#include +#include + +#define EP_IN (2 | FPI_USB_ENDPOINT_IN) +#define EP_OUT (1 | FPI_USB_ENDPOINT_OUT) + +#define EP_IN_MAX_BUF_SIZE 2048 + +#define FP_RTK_CMD_TOTAL_LEN 12 +#define FP_RTK_CMD_LEN 2 +#define FP_RTK_CMD_PARAM_LEN 4 +#define FP_RTK_CMD_ADDR_LEN 4 +#define FP_RTK_CMD_DATA_LEN 2 + +#define TEMPLATE_LEN 35 +#define SUBFACTOR_OFFSET 2 +#define UID_OFFSET 3 +#define UID_PAYLOAD_LEN 32 + +/* Command transfer timeout :ms*/ +#define CMD_TIMEOUT 1000 +#define DATA_TIMEOUT 5000 +#define STATUS_TIMEOUT 2000 + +#define MAX_ENROLL_SAMPLES 8 +#define DEFAULT_UID_LEN 28 +#define SUB_FINGER_01 0xFF + +#define GET_CMD_TYPE(val) ((val & 0xC0) >> 6) +#define GET_TRANS_DATA_LEN(len_h, len_l) ((len_h << 8) | len_l) +#define GET_LEN_L(total_data_len) ((total_data_len) & 0xff) +#define GET_LEN_H(total_data_len) ((total_data_len) >> 8) + +G_DECLARE_FINAL_TYPE (FpiDeviceRealtek, fpi_device_realtek, FPI, DEVICE_REALTEK, FpDevice) + +typedef void (*SynCmdMsgCallback) (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error); + +typedef struct +{ + SynCmdMsgCallback callback; +} CommandData; + +typedef enum { + FP_RTK_CMD_ONLY = 0, + FP_RTK_CMD_READ, + FP_RTK_CMD_WRITE, +} FpRtkCmdType; + +typedef enum { + FP_RTK_MSG_PLAINTEXT = 0, + FP_RTK_MSG_PLAINTEXT_NO_STATUS, +} FpRtkMsgType; + +typedef enum { + FP_RTK_PURPOSE_IDENTIFY = 0x01, /* identify before enroll */ + FP_RTK_PURPOSE_VERIFY = 0x02, + FP_RTK_PURPOSE_ENROLL = 0x04, +} FpRtkPurpose; + +typedef enum { + FP_RTK_SUCCESS = 0x0, + FP_RTK_TOO_HIGH, + FP_RTK_TOO_LOW, + FP_RTK_TOO_LEFT, + FP_RTK_TOO_RIGHT, + FP_RTK_TOO_FAST, + FP_RTK_TOO_SLOW, + FP_RTK_POOR_QUALITY, + FP_RTK_TOO_SKEWED, + FP_RTK_TOO_SHORT, + FP_RTK_MERGE_FAILURE, + FP_RTK_MATCH_FAIL, + FP_RTK_CMD_ERR, +} FpRtkInStatus; + +typedef enum { + FP_RTK_ENROLL_GET_TEMPLATE = 0, + FP_RTK_ENROLL_BEGIN_POS, + FP_RTK_ENROLL_CAPTURE, + FP_RTK_ENROLL_FINISH_CAPTURE, + FP_RTK_ENROLL_ACCEPT_SAMPLE, + FP_RTK_ENROLL_CHECK_DUPLICATE, + FP_RTK_ENROLL_COMMIT, + FP_RTK_ENROLL_NUM_STATES, +} FpRtkEnrollState; + +typedef enum { + FP_RTK_VERIFY_CAPTURE = 0, + FP_RTK_VERIFY_FINISH_CAPTURE, + FP_RTK_VERIFY_ACCEPT_SAMPLE, + FP_RTK_VERIFY_INDENTIFY_FEATURE, + FP_RTK_VERIFY_UPDATE_TEMPLATE, + FP_RTK_VERIFY_NUM_STATES, +} FpRtkVerifyState; + +typedef enum { + FP_RTK_DELETE_GET_POS = 0, + FP_RTK_DELETE_PRINT, + FP_RTK_DELETE_NUM_STATES, +} FpRtkDeleteState; + +typedef enum { + FP_RTK_INIT_SELECT_OS = 0, + FP_RTK_INIT_GET_ENROLL_NUM, + FP_RTK_INIT_NUM_STATES, +} FpRtkInitState; + +typedef enum { + FP_RTK_CMD_SEND = 0, + FP_RTK_CMD_TRANS_DATA, + FP_RTK_CMD_GET_STATUS, + FP_RTK_CMD_NUM_STATES, +} FpRtkCmdState; + +struct _FpiDeviceRealtek +{ + FpDevice parent; + FpiSsm *task_ssm; + FpiSsm *cmd_ssm; + FpiUsbTransfer *cmd_transfer; + FpiUsbTransfer *data_transfer; + gint cmd_type; + FpRtkMsgType message_type; + gboolean cmd_cancellable; + gint enroll_stage; + gint max_enroll_stage; + guchar *read_data; + gsize trans_data_len; + FpRtkPurpose fp_purpose; + gint pos_index; + gint template_num; +}; + +struct realtek_fp_cmd +{ + uint8_t cmd[FP_RTK_CMD_LEN]; + uint8_t param[FP_RTK_CMD_PARAM_LEN]; + uint8_t addr[FP_RTK_CMD_ADDR_LEN]; + uint8_t data_len[FP_RTK_CMD_DATA_LEN]; +}; + +static struct realtek_fp_cmd co_start_capture = { + .cmd = {0x05, 0x05}, +}; + +static struct realtek_fp_cmd co_finish_capture = { + .cmd = {0x45, 0x06}, + .data_len = {0x05}, +}; + +static struct realtek_fp_cmd co_accept_sample = { + .cmd = {0x45, 0x08}, + .data_len = {0x09}, +}; + +static struct realtek_fp_cmd tls_identify_feature = { + .cmd = {0x45, 0x22}, + .data_len = {0x2A}, +}; + +static struct realtek_fp_cmd co_get_enroll_num = { + .cmd = {0x45, 0x0d}, + .data_len = {0x02}, +}; + +static struct realtek_fp_cmd co_get_template = { + .cmd = {0x45, 0x0E}, +}; + +static struct realtek_fp_cmd tls_enroll_begin = { + .cmd = {0x05, 0x20}, +}; + +static struct realtek_fp_cmd co_check_duplicate = { + .cmd = {0x45, 0x10}, + .data_len = {0x22}, +}; + +static struct realtek_fp_cmd tls_enroll_commit = { + .cmd = {0x85, 0x21}, + .data_len = {0x20}, +}; + +static struct realtek_fp_cmd co_update_template = { + .cmd = {0x05, 0x11}, +}; + +static struct realtek_fp_cmd co_delete_record = { + .cmd = {0x05, 0x0F}, +}; + +static struct realtek_fp_cmd co_select_system = { + .cmd = {0x05, 0x13}, +}; \ No newline at end of file diff --git a/libfprint/meson.build b/libfprint/meson.build index 7e3b771f..c2ebd8c1 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -141,6 +141,8 @@ driver_sources = { [ 'drivers/goodixmoc/goodix.c', 'drivers/goodixmoc/goodix_proto.c' ], 'fpcmoc' : [ 'drivers/fpcmoc/fpc.c' ], + 'realtek' : + [ 'drivers/realtek/realtek.c' ], } helper_sources = { diff --git a/meson.build b/meson.build index 0b7569bd..9fc10315 100644 --- a/meson.build +++ b/meson.build @@ -131,6 +131,7 @@ default_drivers = [ 'goodixmoc', 'nb1010', 'fpcmoc', + 'realtek', # SPI 'elanspi', diff --git a/tests/meson.build b/tests/meson.build index 31040d1d..f4f15979 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -52,6 +52,7 @@ drivers_tests = [ 'nb1010', 'egis0570', 'fpcmoc', + 'realtek', ] if get_option('introspection') diff --git a/tests/realtek/custom.pcapng b/tests/realtek/custom.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..0fe6dc7b05d6bdd1008665467bffca83d04835b1 GIT binary patch literal 29128 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 literal 0 HcmV?d00001 diff --git a/tests/realtek/custom.py b/tests/realtek/custom.py new file mode 100755 index 00000000..15a71e2c --- /dev/null +++ b/tests/realtek/custom.py @@ -0,0 +1,110 @@ +#!/usr/bin/python3 + +import traceback +import sys +import gi + +gi.require_version('FPrint', '2.0') +from gi.repository import FPrint, GLib + +# Exit with error on any exception, included those happening in async callbacks +sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1)) + +ctx = GLib.main_context_default() + +c = FPrint.Context() +c.enumerate() +devices = c.get_devices() + +d = devices[0] +del devices + +assert d.get_driver() == "realtek" +assert not d.has_feature(FPrint.DeviceFeature.CAPTURE) +assert d.has_feature(FPrint.DeviceFeature.IDENTIFY) +assert d.has_feature(FPrint.DeviceFeature.VERIFY) +assert not d.has_feature(FPrint.DeviceFeature.DUPLICATES_CHECK) +assert d.has_feature(FPrint.DeviceFeature.STORAGE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_LIST) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) + +d.open_sync() + +# 1. verify clear storage command, 2. make sure later asserts are good +d.clear_storage_sync() + +template = FPrint.Print.new(d) + +def enroll_progress(*args): + # assert d.get_finger_status() & FPrint.FingerStatusFlags.NEEDED + print('enroll progress: ' + str(args)) + +def identify_done(dev, res): + global identified + identified = True + try: + identify_match, identify_print = dev.identify_finish(res) + except gi.repository.GLib.GError as e: + print("Please try again") + else: + print('indentification_done: ', identify_match, identify_print) + assert identify_match.equal(identify_print) + +def start_identify_async(prints): + global identified + print('async identifying') + d.identify(prints, callback=identify_done) + del prints + + while not identified: + ctx.iteration(True) + + identified = False + +# List, enroll, list, verify, identify, delete +print("enrolling") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll done") + +print("listing") +stored = d.list_prints_sync() +print("listing done") +assert len(stored) == 1 +assert stored[0].equal(p) +print("verifying") +try: + assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE + verify_res, verify_print = d.verify_sync(p) + assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +except gi.repository.GLib.GError as e: + print("Please try again") +else: + print("verify done") + del p + assert verify_res == True + +identified = False +deserialized_prints = [] +for p in stored: + deserialized_prints.append(FPrint.Print.deserialize(p.serialize())) + assert deserialized_prints[-1].equal(p) +del stored + +print('async identifying') +d.identify(deserialized_prints, callback=identify_done) +del deserialized_prints + +while not identified: + ctx.iteration(True) + +print("deleting") +d.delete_print_sync(p) +print("delete done") + +d.close_sync() + +del d +del c diff --git a/tests/realtek/device b/tests/realtek/device new file mode 100644 index 00000000..c4f1c85e --- /dev/null +++ b/tests/realtek/device @@ -0,0 +1,240 @@ +P: /devices/pci0000:00/0000:00:14.0/usb1/1-4 +N: bus/usb/001/005=12010102EF020140DA0B135801210301020109022E00010104A0FA0904000004FF02000507050102000200070583031000080705840310000807058202000200 +E: DEVNAME=/dev/bus/usb/001/005 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=bda/5813/2101 +E: TYPE=239/2/1 +E: BUSNUM=001 +E: DEVNUM=005 +E: MAJOR=189 +E: MINOR=4 +E: SUBSYSTEM=usb +E: ID_VENDOR=Generic +E: ID_VENDOR_ENC=Generic +E: ID_VENDOR_ID=0bda +E: ID_MODEL=Realtek_USB2.0_Finger_Print_Bridge +E: ID_MODEL_ENC=Realtek\x20USB2.0\x20Finger\x20Print\x20Bridge +E: ID_MODEL_ID=5813 +E: ID_REVISION=2101 +E: ID_SERIAL=Generic_Realtek_USB2.0_Finger_Print_Bridge_201801010001 +E: ID_SERIAL_SHORT=201801010001 +E: ID_BUS=usb +E: ID_USB_INTERFACES=:ff0200: +E: ID_VENDOR_FROM_DATABASE=Realtek Semiconductor Corp. +E: ID_PATH=pci-0000:00:14.0-usb-0:4 +E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_4 +A: authorized=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=ef\n +A: bDeviceProtocol=01\n +A: bDeviceSubClass=02\n +A: bMaxPacketSize0=64\n +A: bMaxPower=500mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=2101\n +A: bmAttributes=a0\n +A: busnum=1\n +A: configuration=Realtek USB2.0 Finger Print Bridge\n +H: descriptors=12010102EF020140DA0B135801210301020109022E00010104A0FA0904000004FF02000507050102000200070583031000080705840310000807058202000200 +A: dev=189:4\n +A: devnum=5\n +A: devpath=4\n +L: driver=../../../../../bus/usb/drivers/usb +L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4b/device:4c/device:50 +A: idProduct=5813\n +A: idVendor=0bda\n +A: ltm_capable=no\n +A: manufacturer=Generic\n +A: maxchild=0\n +A: physical_location/dock=no\n +A: physical_location/horizontal_position=left\n +A: physical_location/lid=no\n +A: physical_location/panel=top\n +A: physical_location/vertical_position=upper\n +L: port=../1-0:1.0/usb1-port4 +A: power/active_duration=91232868\n +A: power/async=enabled\n +A: power/autosuspend=2\n +A: power/autosuspend_delay_ms=2000\n +A: power/connected_duration=91232868\n +A: power/control=on\n +A: power/level=on\n +A: power/persist=1\n +A: power/runtime_active_kids=0\n +A: power/runtime_active_time=91232594\n +A: power/runtime_enabled=forbidden\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=7\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=Realtek USB2.0 Finger Print Bridge\n +A: quirks=0x0\n +A: removable=removable\n +A: rx_lanes=1\n +A: serial=201801010001\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=15076313\n +A: version= 2.01\n + +P: /devices/pci0000:00/0000:00:14.0/usb1 +N: bus/usb/001/001=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C +E: DEVNAME=/dev/bus/usb/001/001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=1d6b/2/602 +E: TYPE=9/0/1 +E: BUSNUM=001 +E: DEVNUM=001 +E: MAJOR=189 +E: MINOR=0 +E: SUBSYSTEM=usb +E: ID_VENDOR=Linux_6.2.0-35-generic_xhci-hcd +E: ID_VENDOR_ENC=Linux\x206.2.0-35-generic\x20xhci-hcd +E: ID_VENDOR_ID=1d6b +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_ID=0002 +E: ID_REVISION=0602 +E: ID_SERIAL=Linux_6.2.0-35-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_SERIAL_SHORT=0000:00:14.0 +E: ID_BUS=usb +E: ID_USB_INTERFACES=:090000: +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_AUTOSUSPEND=1 +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_PATH=pci-0000:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_FOR_SEAT=usb-pci-0000_00_14_0 +E: TAGS=:seat: +E: CURRENT_TAGS=:seat: +A: authorized=1\n +A: authorized_default=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=09\n +A: bDeviceProtocol=01\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=0mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0602\n +A: bmAttributes=e0\n +A: busnum=1\n +A: configuration= +H: descriptors=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C +A: dev=189:0\n +A: devnum=1\n +A: devpath=0\n +L: driver=../../../../bus/usb/drivers/usb +L: firmware_node=../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4b/device:4c +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 6.2.0-35-generic xhci-hcd\n +A: maxchild=16\n +A: power/active_duration=264747968\n +A: power/async=enabled\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=264747968\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_kids=3\n +A: power/runtime_active_time=264747968\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=0\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=xHCI Host Controller\n +A: quirks=0x0\n +A: removable=unknown\n +A: rx_lanes=1\n +A: serial=0000:00:14.0\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=3177\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0 +E: DRIVER=xhci_hcd +E: PCI_CLASS=C0330 +E: PCI_ID=8086:A36D +E: PCI_SUBSYS_ID=1028:085C +E: PCI_SLOT_NAME=0000:00:14.0 +E: MODALIAS=pci:v00008086d0000A36Dsv00001028sd0000085Cbc0Csc03i30 +E: SUBSYSTEM=pci +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_VENDOR_FROM_DATABASE=Intel Corporation +E: ID_MODEL_FROM_DATABASE=Cannon Lake PCH USB 3.1 xHCI Host Controller +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=86806DA3060590021030030C00008000040030D200000000000000000000000000000000000000000000000028105C08000000007000000000000000FF010000FD0134808FC6FF8300000000000000007F6DDC0F000000005919041B00000000316000000000000000000000000000000180C2C108000000000000000000000005908700D802E0FE0000000000000000090014F01000400100000000C10A080000080E00001800008F40020000010000030000000C00000000000000C000000000000000000100003000000000000000030000000C0000000000000000000000000000000000000000000000000000000000000000000000B50F120112000000 +A: consistent_dma_mask_bits=64\n +A: d3cold_allowed=1\n +A: dbc=disabled\n +A: device=0xa36d\n +A: dma_mask_bits=64\n +L: driver=../../../bus/pci/drivers/xhci_hcd +A: driver_override=(null)\n +A: enable=1\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4b +A: index=4\n +A: irq=125\n +A: label=Onboard - Other\n +A: local_cpulist=0-3\n +A: local_cpus=f\n +A: modalias=pci:v00008086d0000A36Dsv00001028sd0000085Cbc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/125=msi\n +A: numa_node=-1\n +A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 6 7 2112 7\nxHCI ring segments 24 24 4096 24\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 12 32 128 1\nbuffer-32 0 0 32 0\n +A: power/async=enabled\n +A: power/control=on\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=264748677\n +A: power/runtime_enabled=forbidden\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=1\n +A: power/wakeup=enabled\n +A: power/wakeup_abort_count=0\n +A: power/wakeup_active=0\n +A: power/wakeup_active_count=0\n +A: power/wakeup_count=0\n +A: power/wakeup_expire_count=0\n +A: power/wakeup_last_time_ms=0\n +A: power/wakeup_max_time_ms=0\n +A: power/wakeup_total_time_ms=0\n +A: power_state=D0\n +A: resource=0x00000000d2300000 0x00000000d230ffff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n +A: revision=0x10\n +A: subsystem_device=0x085c\n +A: subsystem_vendor=0x1028\n +A: vendor=0x8086\n + From 16d02b3ed5f77c40e231a2049b492641b6b797ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 10 Oct 2023 16:48:37 +0200 Subject: [PATCH 20/79] fp-image: Remove unused ref_count flag It's an object so we already ref-count it. --- libfprint/fpi-image.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libfprint/fpi-image.h b/libfprint/fpi-image.h index 3554bb7b..5cdf4e82 100644 --- a/libfprint/fpi-image.h +++ b/libfprint/fpi-image.h @@ -67,7 +67,6 @@ struct _FpImage guint8 *binarized; GPtrArray *minutiae; - guint ref_count; }; gint fpi_std_sq_dev (const guint8 *buf, From 83939abe10ea7d9f3b851a1d8455072d63521768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 10 Oct 2023 16:49:16 +0200 Subject: [PATCH 21/79] fp-image: Add FP_IMAGE_NONE flags definition --- libfprint/fpi-image.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libfprint/fpi-image.h b/libfprint/fpi-image.h index 5cdf4e82..e9ff803d 100644 --- a/libfprint/fpi-image.h +++ b/libfprint/fpi-image.h @@ -33,6 +33,7 @@ * rely on the image to be normalized by libfprint before further processing. */ typedef enum { + FPI_IMAGE_NONE = 0, FPI_IMAGE_V_FLIPPED = 1 << 0, FPI_IMAGE_H_FLIPPED = 1 << 1, FPI_IMAGE_COLORS_INVERTED = 1 << 2, From 2b008b52d707004f6fae15ab7f14a278277ab530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 10 Oct 2023 16:51:09 +0200 Subject: [PATCH 22/79] fp-image: Simplify minutiae detection tasks We can just use a GTask to handle the detection while using the finish function to process the results to the image, so that it is more predictable when this happens and it does not depend on a thread returning. Also remove data duplication when possible, this class wasn't fully safe anyway to be used concurrently, so there's no point to copy data when not needed. Also added the hard constraint to not proceed with minutiae detection if something else is already doing this. At the same time we can mark the task to finish early on cancellation. --- libfprint/fp-image.c | 189 +++++++++++++++++++++++++----------------- libfprint/fpi-image.h | 2 + 2 files changed, 116 insertions(+), 75 deletions(-) diff --git a/libfprint/fp-image.c b/libfprint/fp-image.c index 8870cfab..4c4864fd 100644 --- a/libfprint/fp-image.c +++ b/libfprint/fp-image.c @@ -160,60 +160,65 @@ fp_image_init (FpImage *self) typedef struct { - GAsyncReadyCallback user_cb; struct fp_minutiae *minutiae; - gint width, height; - gdouble ppmm; - FpiImageFlags flags; - guchar *image; guchar *binarized; -} DetectMinutiaeData; + FpiImageFlags flags; + unsigned char *image; + gboolean image_changed; +} DetectMinutiaeNbisData; static void -fp_image_detect_minutiae_free (DetectMinutiaeData *data) +fp_image_detect_minutiae_free (DetectMinutiaeNbisData *data) { - g_clear_pointer (&data->image, g_free); g_clear_pointer (&data->minutiae, free_minutiae); g_clear_pointer (&data->binarized, g_free); + + if (data->image_changed) + g_clear_pointer (&data->image, g_free); + g_free (data); } -static void -fp_image_detect_minutiae_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (DetectMinutiaeNbisData, fp_image_detect_minutiae_free) + + +static gboolean +fp_image_detect_minutiae_nbis_finish (FpImage *self, + GTask *task, + GError **error) { - GTask *task = G_TASK (res); - FpImage *image; - DetectMinutiaeData *data = g_task_get_task_data (task); + g_autoptr(DetectMinutiaeNbisData) data = NULL; - if (!g_task_had_error (task)) + data = g_task_propagate_pointer (task, error); + + if (data != NULL) { - gint i; - image = FP_IMAGE (source_object); + self->flags = data->flags; - image->flags = data->flags; + if (data->image_changed) + { + g_clear_pointer (&self->data, g_free); + self->data = g_steal_pointer (&data->image); + } - g_clear_pointer (&image->data, g_free); - image->data = g_steal_pointer (&data->image); + g_clear_pointer (&self->binarized, g_free); + self->binarized = g_steal_pointer (&data->binarized); - g_clear_pointer (&image->binarized, g_free); - image->binarized = g_steal_pointer (&data->binarized); + g_clear_pointer (&self->minutiae, g_ptr_array_unref); + self->minutiae = g_ptr_array_new_full (data->minutiae->num, + (GDestroyNotify) free_minutia); - g_clear_pointer (&image->minutiae, g_ptr_array_unref); - image->minutiae = g_ptr_array_new_full (data->minutiae->num, - (GDestroyNotify) free_minutia); - - for (i = 0; i < data->minutiae->num; i++) - g_ptr_array_add (image->minutiae, + for (int i = 0; i < data->minutiae->num; i++) + g_ptr_array_add (self->minutiae, g_steal_pointer (&data->minutiae->list[i])); - /* Don't let it delete anything. */ + /* Don't let free_minutiae delete the minutiae that we now own. */ data->minutiae->num = 0; + + return TRUE; } - if (data->user_cb) - data->user_cb (source_object, res, user_data); + return FALSE; } static void @@ -266,70 +271,83 @@ invert_colors (guint8 *data, gint width, gint height) } static void -fp_image_detect_minutiae_thread_func (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) +fp_image_detect_minutiae_nbis_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) { g_autoptr(GTimer) timer = NULL; - DetectMinutiaeData *data = task_data; - struct fp_minutiae *minutiae = NULL; + g_autoptr(DetectMinutiaeNbisData) ret_data = NULL; + g_autoptr(GTask) thread_task = g_steal_pointer (&task); g_autofree gint *direction_map = NULL; g_autofree gint *low_contrast_map = NULL; g_autofree gint *low_flow_map = NULL; g_autofree gint *high_curve_map = NULL; g_autofree gint *quality_map = NULL; - g_autofree guchar *bdata = NULL; + g_autofree LFSPARMS *lfsparms = NULL; + FpImage *self = source_object; + FpiImageFlags minutiae_flags; + unsigned char *image; gint map_w, map_h; gint bw, bh, bd; gint r; - g_autofree LFSPARMS *lfsparms = NULL; + + image = self->data; + minutiae_flags = self->flags & ~(FPI_IMAGE_H_FLIPPED | + FPI_IMAGE_V_FLIPPED | + FPI_IMAGE_COLORS_INVERTED); + + if (minutiae_flags != FPI_IMAGE_NONE) + image = g_memdup2 (self->data, self->width * self->height); + + ret_data = g_new0 (DetectMinutiaeNbisData, 1); + ret_data->flags = minutiae_flags; + ret_data->image = image; + ret_data->image_changed = image != self->data; /* Normalize the image first */ - if (data->flags & FPI_IMAGE_H_FLIPPED) - hflip (data->image, data->width, data->height); + if (self->flags & FPI_IMAGE_H_FLIPPED) + hflip (image, self->width, self->height); - if (data->flags & FPI_IMAGE_V_FLIPPED) - vflip (data->image, data->width, data->height); + if (self->flags & FPI_IMAGE_V_FLIPPED) + vflip (image, self->width, self->height); - if (data->flags & FPI_IMAGE_COLORS_INVERTED) - invert_colors (data->image, data->width, data->height); - - data->flags &= ~(FPI_IMAGE_H_FLIPPED | FPI_IMAGE_V_FLIPPED | FPI_IMAGE_COLORS_INVERTED); + if (self->flags & FPI_IMAGE_COLORS_INVERTED) + invert_colors (image, self->width, self->height); lfsparms = g_memdup2 (&g_lfsparms_V2, sizeof (LFSPARMS)); - lfsparms->remove_perimeter_pts = data->flags & FPI_IMAGE_PARTIAL ? TRUE : FALSE; + lfsparms->remove_perimeter_pts = minutiae_flags & FPI_IMAGE_PARTIAL ? TRUE : FALSE; timer = g_timer_new (); - r = get_minutiae (&minutiae, &quality_map, &direction_map, + r = get_minutiae (&ret_data->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); + &map_w, &map_h, &ret_data->binarized, &bw, &bh, &bd, + image, self->width, self->height, 8, + self->ppmm, lfsparms); g_timer_stop (timer); fp_dbg ("Minutiae scan completed in %f secs", g_timer_elapsed (timer, NULL)); - data->binarized = g_steal_pointer (&bdata); - data->minutiae = minutiae; + if (g_task_had_error (thread_task)) + return; if (r) { fp_err ("get minutiae failed, code %d", r); - g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Minutiae scan failed with code %d", r); - g_object_unref (task); + g_task_return_new_error (thread_task, G_IO_ERROR, + G_IO_ERROR_FAILED, + "Minutiae scan failed with code %d", r); return; } - if (!data->minutiae || data->minutiae->num == 0) + if (!ret_data->minutiae || ret_data->minutiae->num == 0) { - g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, + g_task_return_new_error (thread_task, G_IO_ERROR, G_IO_ERROR_FAILED, "No minutiae found"); - g_object_unref (task); return; } - g_task_return_boolean (task, TRUE); - g_object_unref (task); + g_task_return_pointer (thread_task, g_steal_pointer (&ret_data), + (GDestroyNotify) fp_image_detect_minutiae_free); } /** @@ -445,21 +463,22 @@ fp_image_detect_minutiae (FpImage *self, GAsyncReadyCallback callback, gpointer user_data) { - GTask *task; - DetectMinutiaeData *data = g_new0 (DetectMinutiaeData, 1); + g_autoptr(GTask) task = NULL; - task = g_task_new (self, cancellable, fp_image_detect_minutiae_cb, user_data); + g_return_if_fail (FP_IS_IMAGE (self)); + g_return_if_fail (callback != NULL); - data->image = g_malloc (self->width * self->height); - memcpy (data->image, self->data, self->width * self->height); - data->flags = self->flags; - data->width = self->width; - data->height = self->height; - data->ppmm = self->ppmm; - data->user_cb = callback; + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, fp_image_detect_minutiae); + g_task_set_check_cancellable (task, TRUE); - g_task_set_task_data (task, data, (GDestroyNotify) fp_image_detect_minutiae_free); - g_task_run_in_thread (task, fp_image_detect_minutiae_thread_func); + if (!g_atomic_int_compare_and_exchange (&self->detection_in_progress, + FALSE, TRUE)) + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE, + "Minutiae detection is already in progress"); + + g_task_run_in_thread (g_steal_pointer (&task), + fp_image_detect_minutiae_nbis_thread_func); } /** @@ -477,7 +496,27 @@ fp_image_detect_minutiae_finish (FpImage *self, GAsyncResult *result, GError **error) { - return g_task_propagate_boolean (G_TASK (result), error); + GTask *task; + gboolean changed; + + g_return_val_if_fail (FP_IS_IMAGE (self), FALSE); + g_return_val_if_fail (g_task_is_valid (result, self), FALSE); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == + fp_image_detect_minutiae, FALSE); + + task = G_TASK (result); + changed = g_atomic_int_compare_and_exchange (&self->detection_in_progress, + TRUE, FALSE); + g_assert (changed); + + if (g_task_had_error (task)) + { + gpointer data = g_task_propagate_pointer (task, error); + g_assert (data == NULL); + return FALSE; + } + + return fp_image_detect_minutiae_nbis_finish (self, task, error); } /** diff --git a/libfprint/fpi-image.h b/libfprint/fpi-image.h index e9ff803d..0c703fb1 100644 --- a/libfprint/fpi-image.h +++ b/libfprint/fpi-image.h @@ -68,6 +68,8 @@ struct _FpImage guint8 *binarized; GPtrArray *minutiae; + + gboolean detection_in_progress; }; gint fpi_std_sq_dev (const guint8 *buf, From 5a1253e37c52f0aef50ccc8ac8bd61af8d123f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 24 Nov 2023 21:02:50 +0100 Subject: [PATCH 23/79] fp-image: Do not start thread detection task thread if already running --- libfprint/fp-image.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libfprint/fp-image.c b/libfprint/fp-image.c index 4c4864fd..f9c60b37 100644 --- a/libfprint/fp-image.c +++ b/libfprint/fp-image.c @@ -474,8 +474,11 @@ fp_image_detect_minutiae (FpImage *self, if (!g_atomic_int_compare_and_exchange (&self->detection_in_progress, FALSE, TRUE)) - g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE, - "Minutiae detection is already in progress"); + { + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE, + "Minutiae detection is already in progress"); + return; + } g_task_run_in_thread (g_steal_pointer (&task), fp_image_detect_minutiae_nbis_thread_func); From d3ec9a80d34bed983fa1b8cd1bd6a16896272ec8 Mon Sep 17 00:00:00 2001 From: Mohammed Anas Date: Mon, 22 Jan 2024 15:27:33 +0000 Subject: [PATCH 24/79] tests: remove Bash dependency in favor of `sh` The script works just fine with `sh`. Also replace nonstandard `test` operator `==` with the standard `=`. The other changes are mostly cosmetic. --- tests/test-generated-hwdb.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test-generated-hwdb.sh b/tests/test-generated-hwdb.sh index 7e1af144..b23f17c2 100755 --- a/tests/test-generated-hwdb.sh +++ b/tests/test-generated-hwdb.sh @@ -1,12 +1,11 @@ -#!/usr/bin/env bash -set -e +#!/bin/sh -e if [ ! -x "$UDEV_HWDB" ]; then echo "E: UDEV_HWDB (${UDEV_HWDB}) unset or not executable." exit 1 fi -if [ "$UDEV_HWDB_CHECK_CONTENTS" == 1 ]; then +if [ "$UDEV_HWDB_CHECK_CONTENTS" = 1 ]; then generated_rules=$(mktemp "${TMPDIR:-/tmp}/libfprint-XXXXXX.hwdb") else generated_rules=/dev/null From 1701d72ff9663b63f363e0c9feeb55bad6bcbe25 Mon Sep 17 00:00:00 2001 From: Mohammed Anas Date: Mon, 22 Jan 2024 15:18:27 +0000 Subject: [PATCH 25/79] tests: make `mktemp` command call work with Chimera Linux's `mktemp` On Chimera Linux, which uses FreeBSD's userland tools, the original call fails with the following error: mktemp: mkstemp failed on /tmp/libfprint-XXXXXX.hwdb: Invalid argument Moving the X's to the end of the template passed to `mktemp` fixes the error, and also works with GNU's `mktemp`. --- tests/test-generated-hwdb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-generated-hwdb.sh b/tests/test-generated-hwdb.sh index b23f17c2..b1031802 100755 --- a/tests/test-generated-hwdb.sh +++ b/tests/test-generated-hwdb.sh @@ -6,7 +6,7 @@ if [ ! -x "$UDEV_HWDB" ]; then fi if [ "$UDEV_HWDB_CHECK_CONTENTS" = 1 ]; then - generated_rules=$(mktemp "${TMPDIR:-/tmp}/libfprint-XXXXXX.hwdb") + generated_rules=$(mktemp "${TMPDIR:-/tmp}/libfprint.hwdb.XXXXXX") else generated_rules=/dev/null fi From 631da4654f1312b2336b0474502386cf46c6e08a Mon Sep 17 00:00:00 2001 From: xiaofei Date: Tue, 8 Nov 2022 10:43:15 +0800 Subject: [PATCH 26/79] focaltechmoc:Support FocalTech moc devices Supported UID: 0x2808 Supported PIDs: 0x9E48, 0xD979, 0xa959 --- data/autosuspend.hwdb | 7 + .../drivers/focaltech_moc/focaltech_moc.c | 1875 +++++++++++++++++ .../drivers/focaltech_moc/focaltech_moc.h | 52 + libfprint/meson.build | 2 + meson.build | 1 + tests/focaltech_moc/custom.pcapng | Bin 0 -> 38864 bytes tests/focaltech_moc/custom.py | 89 + tests/focaltech_moc/device | 385 ++++ tests/meson.build | 1 + 9 files changed, 2412 insertions(+) create mode 100644 libfprint/drivers/focaltech_moc/focaltech_moc.c create mode 100644 libfprint/drivers/focaltech_moc/focaltech_moc.h create mode 100644 tests/focaltech_moc/custom.pcapng create mode 100755 tests/focaltech_moc/custom.py create mode 100644 tests/focaltech_moc/device diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 2b4dc706..44dbbcd3 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -158,6 +158,13 @@ usb:v1C7Ap0603* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver focaltech_moc +usb:v2808p9E48* +usb:v2808pD979* +usb:v2808pA959* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver fpcmoc usb:v10A5pFFE0* usb:v10A5pA305* diff --git a/libfprint/drivers/focaltech_moc/focaltech_moc.c b/libfprint/drivers/focaltech_moc/focaltech_moc.c new file mode 100644 index 00000000..190ab436 --- /dev/null +++ b/libfprint/drivers/focaltech_moc/focaltech_moc.c @@ -0,0 +1,1875 @@ +/* + * Copyright (C) 2022 FocalTech Electronics Inc + * + * 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 + */ + +#include "focaltech_moc.h" + +#include + +#define FP_COMPONENT "focaltech_moc" + +#include "drivers_api.h" + +G_DEFINE_TYPE (FpiDeviceFocaltechMoc, fpi_device_focaltech_moc, FP_TYPE_DEVICE) + +static const FpIdEntry id_table[] = { + { .vid = 0x2808, .pid = 0x9e48, }, + { .vid = 0x2808, .pid = 0xd979, }, + { .vid = 0x2808, .pid = 0xa959, }, + { .vid = 0, .pid = 0, .driver_data = 0 }, /* terminating entry */ +}; + +typedef void (*SynCmdMsgCallback) (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error); + +typedef struct +{ + SynCmdMsgCallback callback; +} CommandData; + +typedef struct +{ + uint8_t h; + uint8_t l; +} FpCmdLen; + +typedef struct +{ + uint8_t magic; + FpCmdLen len; +} FpCmdHeader; + +typedef struct +{ + FpCmdHeader header; + uint8_t code; + uint8_t payload[0]; +} FpCmd; + +typedef struct +{ +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) + uint8_t b0; + uint8_t b1; +#else + uint8_t b1; + uint8_t b0; +#endif +} u16_bytes_t; + +typedef union +{ + u16_bytes_t s; + uint16_t v; +} u_u16_bytes_t; + +static inline uint16_t +get_u16_from_u8_lh (uint8_t l, uint8_t h) +{ + u_u16_bytes_t u_u16_bytes; + + u_u16_bytes.v = 0; + u_u16_bytes.s.b0 = l; + u_u16_bytes.s.b1 = h; + + return u_u16_bytes.v; +} + +static inline uint8_t +get_u8_l_from_u16 (uint16_t v) +{ + u_u16_bytes_t u_u16_bytes; + + u_u16_bytes.v = v; + + return u_u16_bytes.s.b0; +} + +static inline uint8_t +get_u8_h_from_u16 (uint16_t v) +{ + u_u16_bytes_t u_u16_bytes; + + u_u16_bytes.v = v; + + return u_u16_bytes.s.b1; +} + +static uint8_t +fp_cmd_bcc (uint8_t *data, uint16_t len) +{ + int i; + uint8_t bcc = 0; + + for (i = 0; i < len; i++) + bcc ^= data[i]; + + return bcc; +} + +static uint8_t * +focaltech_moc_compose_cmd (uint8_t cmd, const uint8_t *data, uint16_t len) +{ + g_autofree char *cmd_buf = NULL; + FpCmd *fp_cmd = NULL; + uint8_t *bcc = NULL; + uint16_t header_len = len + sizeof (*bcc); + + cmd_buf = g_malloc0 (sizeof (FpCmd) + header_len); + + fp_cmd = (FpCmd *) cmd_buf; + + fp_cmd->header.magic = 0x02; + fp_cmd->header.len.l = get_u8_l_from_u16 (header_len); + fp_cmd->header.len.h = get_u8_h_from_u16 (header_len); + fp_cmd->code = cmd; + + if (data != NULL) + memcpy (fp_cmd->payload, data, len); + + bcc = fp_cmd->payload + len; + *bcc = fp_cmd_bcc ((uint8_t *) &fp_cmd->header.len, bcc - (uint8_t *) &fp_cmd->header.len); + + return g_steal_pointer (&cmd_buf); +} + +static int +focaltech_moc_check_cmd (uint8_t *response_buf, uint16_t len) +{ + int ret = -1; + FpCmd *fp_cmd = NULL; + uint8_t *bcc = NULL; + uint16_t header_len; + uint16_t data_len; + + fp_cmd = (FpCmd *) response_buf; + + if (len < sizeof (FpCmd) + sizeof (*bcc)) + return ret; + + if (fp_cmd->header.magic != 0x02) + return ret; + + header_len = get_u16_from_u8_lh (fp_cmd->header.len.l, fp_cmd->header.len.h); + + if (header_len < sizeof (*bcc)) + return ret; + + if ((sizeof (FpCmd) + header_len) > len) + return ret; + + data_len = header_len - sizeof (*bcc); + + bcc = fp_cmd->payload + data_len; + + if (fp_cmd_bcc ((uint8_t *) &fp_cmd->header.len, + bcc - (uint8_t *) &fp_cmd->header.len) != *bcc) + return ret; + + ret = 0; + return ret; +} + +static void +fp_cmd_receive_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer userdata, + GError *error) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + CommandData *data = userdata; + int ssm_state = 0; + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, g_steal_pointer (&error)); + return; + } + + if (data == NULL) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + ssm_state = fpi_ssm_get_cur_state (transfer->ssm); + + /* skip zero length package */ + if (transfer->actual_length == 0) + { + fpi_ssm_jump_to_state (transfer->ssm, ssm_state); + return; + } + + if (focaltech_moc_check_cmd (transfer->buffer, transfer->actual_length) != 0) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + if (data->callback) + data->callback (self, transfer->buffer, transfer->actual_length, NULL); + + fpi_ssm_mark_completed (transfer->ssm); +} + +typedef enum { + FP_CMD_SEND = 0, + FP_CMD_GET, + FP_CMD_NUM_STATES, +} FpCmdState; + +static void +fp_cmd_run_state (FpiSsm *ssm, + FpDevice *device) +{ + FpiUsbTransfer *transfer; + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_CMD_SEND: + if (self->cmd_transfer) + { + self->cmd_transfer->ssm = ssm; + fpi_usb_transfer_submit (g_steal_pointer (&self->cmd_transfer), + FOCALTECH_MOC_CMD_TIMEOUT, + NULL, + fpi_ssm_usb_transfer_cb, + NULL); + } + else + { + fpi_ssm_next_state (ssm); + } + + break; + + case FP_CMD_GET: + if (self->cmd_len_in == 0) + { + CommandData *data = fpi_ssm_get_data (ssm); + + if (data->callback) + data->callback (self, NULL, 0, 0); + + fpi_ssm_mark_completed (ssm); + return; + } + + transfer = fpi_usb_transfer_new (device); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, self->bulk_in_ep, self->cmd_len_in); + fpi_usb_transfer_submit (transfer, + self->cmd_cancelable ? 0 : FOCALTECH_MOC_CMD_TIMEOUT, + self->cmd_cancelable ? fpi_device_get_cancellable (device) : NULL, + fp_cmd_receive_cb, + fpi_ssm_get_data (ssm)); + break; + + } + +} + +static void +fp_cmd_ssm_done (FpiSsm *ssm, FpDevice *device, GError *error) +{ + g_autoptr(GError) local_error = error; + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + CommandData *data = fpi_ssm_get_data (ssm); + + self->cmd_ssm = NULL; + + if (local_error && data->callback) + data->callback (self, NULL, 0, g_steal_pointer (&local_error)); +} + +static void +fp_cmd_ssm_done_data_free (CommandData *data) +{ + g_free (data); +} + +static void +focaltech_moc_get_cmd (FpDevice *device, guint8 *buffer_out, + gsize length_out, gsize length_in, + gboolean can_be_cancelled, + SynCmdMsgCallback callback) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + g_autoptr(FpiUsbTransfer) transfer = NULL; + CommandData *data = g_new0 (CommandData, 1); + + transfer = fpi_usb_transfer_new (device); + transfer->short_is_error = TRUE; + fpi_usb_transfer_fill_bulk_full (transfer, self->bulk_out_ep, buffer_out, + length_out, g_free); + data->callback = callback; + + self->cmd_transfer = g_steal_pointer (&transfer); + self->cmd_len_in = length_in + 1; + self->cmd_cancelable = can_be_cancelled; + + self->cmd_ssm = fpi_ssm_new (FP_DEVICE (self), + fp_cmd_run_state, + FP_CMD_NUM_STATES); + + fpi_ssm_set_data (self->cmd_ssm, data, (GDestroyNotify) fp_cmd_ssm_done_data_free); + + fpi_ssm_start (self->cmd_ssm, fp_cmd_ssm_done); +} + +struct UserId +{ + uint8_t uid[32]; +}; + +static void +fprint_set_uid (FpPrint *print, uint8_t *uid, size_t size) +{ + GVariant *var_uid; + GVariant *var_data; + + var_uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, uid, size, 1); + var_data = g_variant_new ("(@ay)", var_uid); + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + g_object_set (print, "fpi-data", var_data, NULL); +} + +enum enroll_states { + ENROLL_RSP_RETRY, + ENROLL_RSP_ENROLL_REPORT, + ENROLL_RSP_ENROLL_OK, + ENROLL_RSP_ENROLL_CANCEL_REPORT, +}; + +static void +enroll_status_report (FpiDeviceFocaltechMoc *self, int enroll_status_id, + int data, GError *error) +{ + FpDevice *device = FP_DEVICE (self); + + switch (enroll_status_id) + { + case ENROLL_RSP_RETRY: + { + fpi_device_enroll_progress (device, self->num_frames, NULL, + fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER)); + break; + } + + case ENROLL_RSP_ENROLL_REPORT: + { + fpi_device_enroll_progress (device, self->num_frames, NULL, NULL); + break; + } + + case ENROLL_RSP_ENROLL_OK: + { + FpPrint *print = NULL; + fp_info ("Enrollment was successful!"); + fpi_device_get_enroll_data (device, &print); + fpi_device_enroll_complete (device, g_object_ref (print), NULL); + break; + } + + case ENROLL_RSP_ENROLL_CANCEL_REPORT: + { + fpi_device_enroll_complete (device, NULL, + fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "Enrollment failed (%d) (ENROLL_RSP_ENROLL_CANCEL_REPORT)", + data)); + } + } +} + +static void +task_ssm_done (FpiSsm *ssm, FpDevice *device, GError *error) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + self->num_frames = 0; + self->task_ssm = NULL; + + if (error) + fpi_device_action_error (device, g_steal_pointer (&error)); +} + +static const char * +get_g_usb_device_direction_des (GUsbDeviceDirection dir) +{ + switch (dir) + { + case G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST: + return "G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST"; + + case G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE: + return "G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE"; + + default: + return "unknown"; + } +} + +static int +usb_claim_interface_probe (FpDevice *device, int claim, GError **error) +{ + g_autoptr(GPtrArray) interfaces = NULL; + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + int ret = -1; + int i; + + interfaces = g_usb_device_get_interfaces (fpi_device_get_usb_device (device), error); + + for (i = 0; i < interfaces->len; i++) + { + GUsbInterface *cur_iface = g_ptr_array_index (interfaces, i); + g_autoptr(GPtrArray) endpoints = g_usb_interface_get_endpoints (cur_iface); + + fp_dbg ("class:%x, subclass:%x, protocol:%x", + g_usb_interface_get_class (cur_iface), + g_usb_interface_get_subclass (cur_iface), + g_usb_interface_get_protocol (cur_iface)); + + if (claim == 1) + { + int j; + + for (j = 0; j < endpoints->len; j++) + { + GUsbEndpoint *endpoint = g_ptr_array_index (endpoints, j); + GBytes *bytes = g_usb_endpoint_get_extra (endpoint); + + fp_dbg ("bytes size:%ld", g_bytes_get_size (bytes)); + + fp_dbg ("kind:%x, max packet size:%d, poll interval:%d, refresh:%x, " + "sync address:%x, address:%x, number:%d, direction:%s", + g_usb_endpoint_get_kind (endpoint), + g_usb_endpoint_get_maximum_packet_size (endpoint), + g_usb_endpoint_get_polling_interval (endpoint), + g_usb_endpoint_get_refresh (endpoint), + g_usb_endpoint_get_synch_address (endpoint), + g_usb_endpoint_get_address (endpoint), + g_usb_endpoint_get_number (endpoint), + get_g_usb_device_direction_des (g_usb_endpoint_get_direction (endpoint))); + + if (g_usb_endpoint_get_direction (endpoint) == G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST) + self->bulk_in_ep = g_usb_endpoint_get_address (endpoint); + else + self->bulk_out_ep = g_usb_endpoint_get_address (endpoint); + } + + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), + g_usb_interface_get_number (cur_iface), + 0, error)) + return ret; + } + else if (!g_usb_device_release_interface (fpi_device_get_usb_device (device), + g_usb_interface_get_number (cur_iface), + 0, error)) + { + return ret; + } + + + } + + ret = 0; + + return ret; +} + +static void +task_ssm_init_done (FpiSsm *ssm, FpDevice *device, GError *error) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + if (error) + usb_claim_interface_probe (device, 0, &error); + + fpi_device_open_complete (FP_DEVICE (self), g_steal_pointer (&error)); +} + +struct EnrollTimes +{ + uint8_t enroll_times; +}; + +static void +focaltech_moc_get_enroll_times (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + struct EnrollTimes *enroll_times = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + enroll_times = (struct EnrollTimes *) (fp_cmd + 1); + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + else + { + fp_dbg ("focaltechmoc enroll_times: %d", enroll_times->enroll_times + 1); + fpi_device_set_nr_enroll_stages (FP_DEVICE (self), enroll_times->enroll_times + 1); + fpi_ssm_next_state (self->task_ssm); + } +} + +static void +focaltech_moc_release_finger (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + else + { + fpi_ssm_next_state (self->task_ssm); + } +} + +enum dev_init_states { + DEV_INIT_GET_ENROLL_TIMES, + DEV_INIT_RELEASE_FINGER, + DEV_INIT_STATES, +}; + +static void +dev_init_handler (FpiSsm *ssm, FpDevice *device) +{ + guint8 *cmd_buf = NULL; + uint16_t cmd_len = 0; + uint16_t resp_len = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case DEV_INIT_GET_ENROLL_TIMES: + cmd_len = 0; + resp_len = sizeof (struct EnrollTimes); + cmd_buf = focaltech_moc_compose_cmd (0xa5, NULL, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_get_enroll_times); + break; + + case DEV_INIT_RELEASE_FINGER: + { + uint8_t d1 = 0x78; + cmd_len = sizeof (d1); + resp_len = 0; + cmd_buf = focaltech_moc_compose_cmd (0x82, &d1, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_release_finger); + break; + } + } +} + +static void +focaltech_moc_open (FpDevice *device) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + GError *error = NULL; + + if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) + { + fpi_device_open_complete (FP_DEVICE (self), g_steal_pointer (&error)); + return; + } + + if (usb_claim_interface_probe (device, 1, &error) != 0) + { + fpi_device_open_complete (FP_DEVICE (self), g_steal_pointer (&error)); + return; + } + + self->task_ssm = fpi_ssm_new (FP_DEVICE (self), dev_init_handler, DEV_INIT_STATES); + fpi_ssm_start (self->task_ssm, task_ssm_init_done); +} + +static void +task_ssm_exit_done (FpiSsm *ssm, FpDevice *device, GError *error) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + if (!error) + { + GError *local_error = NULL; + + if (usb_claim_interface_probe (device, 0, &local_error) < 0) + g_propagate_error (&error, g_steal_pointer (&local_error)); + } + + fpi_device_close_complete (FP_DEVICE (self), error); + self->task_ssm = NULL; +} + +enum dev_exit_states { + DEV_EXIT_START, + DEV_EXIT_STATES, +}; + +static void +dev_exit_handler (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case DEV_EXIT_START: + fpi_ssm_next_state (self->task_ssm); + break; + + default: + g_assert_not_reached (); + } +} + +static void +focaltech_moc_close (FpDevice *device) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + fp_info ("Focaltechmoc dev_exit"); + self->task_ssm = fpi_ssm_new (FP_DEVICE (self), dev_exit_handler, DEV_EXIT_STATES); + fpi_ssm_start (self->task_ssm, task_ssm_exit_done); +} + +enum identify_states { + MOC_IDENTIFY_RELEASE_FINGER, + MOC_IDENTIFY_WAIT_FINGER, + MOC_IDENTIFY_WAIT_FINGER_DELAY, + MOC_IDENTIFY_CAPTURE, + MOC_IDENTIFY_MATCH, + MOC_IDENTIFY_NUM_STATES, +}; + +static void +focaltech_moc_identify_wait_finger_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + uint8_t *finger_status = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + finger_status = (uint8_t *) (fp_cmd + 1); + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + else + { + + if (*finger_status == 0x01) + fpi_ssm_jump_to_state (self->task_ssm, MOC_IDENTIFY_CAPTURE); + else + fpi_ssm_jump_to_state (self->task_ssm, MOC_IDENTIFY_WAIT_FINGER_DELAY); + } +} + +static void +focaltech_moc_identify_wait_finger_delay (FpDevice *device, + void *user_data) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + fpi_ssm_jump_to_state (self->task_ssm, MOC_IDENTIFY_WAIT_FINGER); +} + +enum FprintError { + ERROR_NONE, + ERROR_QUALITY, + ERROR_SHORT, + ERROR_LEFT, + ERROR_RIGHT, + ERROR_NONFINGER, + ERROR_NOMOVE, + ERROR_OTHER, +}; + +struct CaptureResult +{ + uint8_t error; + uint8_t remain; +}; + +static void +focaltech_moc_identify_capture_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + struct CaptureResult *capture_result = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + capture_result = (struct CaptureResult *) (fp_cmd + 1); + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + else + { + if (capture_result->error == ERROR_NONE) + { + fpi_ssm_next_state (self->task_ssm); + } + else + { + if (fpi_device_get_current_action (FP_DEVICE (self)) == FPI_DEVICE_ACTION_VERIFY) + { + fpi_device_verify_report (FP_DEVICE (self), FPI_MATCH_ERROR, NULL, error); + fpi_device_verify_complete (FP_DEVICE (self), NULL); + } + else + { + fpi_device_identify_report (FP_DEVICE (self), NULL, NULL, error); + fpi_device_identify_complete (FP_DEVICE (self), NULL); + } + + fpi_ssm_mark_failed (self->task_ssm, fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); + } + } +} + +static void +identify_status_report (FpiDeviceFocaltechMoc *self, FpPrint *print, GError *error) +{ + FpDevice *device = FP_DEVICE (self); + + if (print == NULL) + { + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY) + { + fpi_device_identify_report (device, NULL, NULL, NULL); + fpi_device_identify_complete (device, NULL); + } + else + { + fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL); + fpi_device_verify_complete (device, NULL); + } + } + else + { + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY) + { + GPtrArray *prints; + gboolean found = FALSE; + guint index; + + fpi_device_get_identify_data (device, &prints); + found = g_ptr_array_find_with_equal_func (prints, + print, + (GEqualFunc) fp_print_equal, + &index); + + if (found) + fpi_device_identify_report (device, g_ptr_array_index (prints, index), print, NULL); + else + fpi_device_identify_report (device, NULL, print, NULL); + + fpi_device_identify_complete (device, NULL); + } + else + { + FpPrint *verify_print = NULL; + fpi_device_get_verify_data (device, &verify_print); + + if (fp_print_equal (verify_print, print)) + fpi_device_verify_report (device, FPI_MATCH_SUCCESS, print, NULL); + else + fpi_device_verify_report (device, FPI_MATCH_FAIL, print, NULL); + + fpi_device_verify_complete (device, NULL); + } + } +} + +static void +focaltech_moc_identify_match_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + struct UserId *user_id = NULL; + FpPrint *print = NULL; + + fp_cmd = (FpCmd *) buffer_in; + user_id = (struct UserId *) (fp_cmd + 1); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + if (fp_cmd->code == 0x04) + { + print = fp_print_new (FP_DEVICE (self)); + fprint_set_uid (print, user_id->uid, sizeof (user_id->uid)); + } + + identify_status_report (self, print, error); + + fpi_ssm_next_state (self->task_ssm); +} + +static void +focaltech_identify_run_state (FpiSsm *ssm, FpDevice *device) +{ + guint8 *cmd_buf = NULL; + uint16_t cmd_len = 0; + uint16_t resp_len = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case MOC_IDENTIFY_RELEASE_FINGER: + { + uint8_t d1 = 0x78; + cmd_len = sizeof (d1); + resp_len = 0; + cmd_buf = focaltech_moc_compose_cmd (0x82, &d1, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_release_finger); + break; + } + + case MOC_IDENTIFY_WAIT_FINGER: + { + uint8_t data = 0x02; + cmd_len = sizeof (uint8_t); + resp_len = sizeof (uint8_t); + cmd_buf = focaltech_moc_compose_cmd (0x80, &data, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_identify_wait_finger_cb); + break; + } + + case MOC_IDENTIFY_WAIT_FINGER_DELAY: + fpi_device_add_timeout (device, 50, + focaltech_moc_identify_wait_finger_delay, + NULL, NULL); + break; + + case MOC_IDENTIFY_CAPTURE: + cmd_len = 0; + resp_len = sizeof (struct CaptureResult); + cmd_buf = focaltech_moc_compose_cmd (0xa6, NULL, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_identify_capture_cb); + break; + + case MOC_IDENTIFY_MATCH: + cmd_len = 0; + resp_len = sizeof (struct UserId); + cmd_buf = focaltech_moc_compose_cmd (0xaa, NULL, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_identify_match_cb); + break; + } +} + +static void +focaltech_moc_identify (FpDevice *device) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + self->task_ssm = fpi_ssm_new (device, + focaltech_identify_run_state, + MOC_IDENTIFY_NUM_STATES); + fpi_ssm_start (self->task_ssm, task_ssm_done); +} + +enum moc_enroll_states { + MOC_ENROLL_GET_ENROLLED_INFO, + MOC_ENROLL_GET_ENROLLED_LIST, + MOC_ENROLL_RELEASE_FINGER, + MOC_ENROLL_START_ENROLL, + MOC_ENROLL_WAIT_FINGER, + MOC_ENROLL_WAIT_FINGER_DELAY, + MOC_ENROLL_ENROLL_CAPTURE, + MOC_ENROLL_SET_ENROLLED_INFO, + MOC_ENROLL_COMMIT_RESULT, + MOC_ENROLL_NUM_STATES, +}; + +struct EnrolledInfoItem +{ + uint8_t uid[FOCALTECH_MOC_UID_PREFIX_LENGTH]; + uint8_t user_id[FOCALTECH_MOC_USER_ID_LENGTH]; +}; + +struct UserDes +{ + uint8_t finger; + char username[FOCALTECH_MOC_USER_ID_LENGTH]; +}; + +struct EnrolledInfo +{ + uint8_t actived[FOCALTECH_MOC_MAX_FINGERS]; + struct EnrolledInfoItem items[FOCALTECH_MOC_MAX_FINGERS]; + struct UserId user_id[FOCALTECH_MOC_MAX_FINGERS]; + struct UserDes user_des[FOCALTECH_MOC_MAX_FINGERS]; +}; + +typedef struct +{ + GPtrArray *list_result; + struct EnrolledInfo *enrolled_info; +} FpActionData; + +struct EnrolledInfoSetData +{ + uint8_t data; + struct EnrolledInfoItem items[FOCALTECH_MOC_MAX_FINGERS]; +}; + +static void +fp_action_ssm_done_data_free (FpActionData *data) +{ + g_clear_pointer (&data->list_result, g_ptr_array_unref); + g_clear_pointer (&data->enrolled_info, g_free); + g_free (data); +} + +static void +focaltech_moc_get_enrolled_info_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + struct EnrolledInfoItem *items = NULL; + FpActionData *data = fpi_ssm_get_data (self->task_ssm); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + items = (struct EnrolledInfoItem *) (fp_cmd + 1); + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + else + { + memcpy (&data->enrolled_info->items[0], items, + FOCALTECH_MOC_MAX_FINGERS * sizeof (struct EnrolledInfoItem)); + fpi_ssm_next_state (self->task_ssm); + } +} + +struct UidList +{ + uint8_t actived[FOCALTECH_MOC_MAX_FINGERS]; + struct UserId uid[FOCALTECH_MOC_MAX_FINGERS]; +}; + +static int +focaltech_moc_get_enrolled_info_item (FpiDeviceFocaltechMoc *self, + uint8_t *uid, + struct EnrolledInfoItem **pitem, int *index) +{ + FpActionData *data = fpi_ssm_get_data (self->task_ssm); + int ret = -1; + int i; + + for (i = 0; i < FOCALTECH_MOC_MAX_FINGERS; i++) + { + struct EnrolledInfoItem *item = &data->enrolled_info->items[i]; + + if (memcmp (item->uid, uid, FOCALTECH_MOC_UID_PREFIX_LENGTH) == 0) + { + data->enrolled_info->actived[i] = 1; + *pitem = item; + *index = i; + ret = 0; + break; + } + } + + return ret; +} + +static void +focaltech_moc_get_enrolled_list_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + struct UidList *uid_list = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + uid_list = (struct UidList *) (fp_cmd + 1); + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + else + { + FpActionData *data = fpi_ssm_get_data (self->task_ssm); + int i; + + for (i = 0; i < FOCALTECH_MOC_MAX_FINGERS; i++) + { + if (uid_list->actived[i] != 0) + { + struct UserId *user_id = &uid_list->uid[i]; + FpPrint *print = fp_print_new (FP_DEVICE (self)); + struct EnrolledInfoItem *item = NULL; + int index; + + fp_info ("focaltechmoc add slot: %d", i); + + fprint_set_uid (print, user_id->uid, sizeof (user_id->uid)); + + if (focaltech_moc_get_enrolled_info_item (self, user_id->uid, &item, &index) == 0) + { + g_autofree gchar *userid_safe = NULL; + const gchar *username; + userid_safe = g_strndup ((const char *) &item->user_id, FOCALTECH_MOC_USER_ID_LENGTH); + fp_dbg ("%s", userid_safe); + fpi_print_fill_from_user_id (print, userid_safe); + memcpy (data->enrolled_info->user_id[index].uid, user_id->uid, 32); + data->enrolled_info->user_des[index].finger = fp_print_get_finger (print); + username = fp_print_get_username (print); + + if (username != NULL) + strncpy (data->enrolled_info->user_des[index].username, username, 64); + } + + g_ptr_array_add (data->list_result, g_object_ref_sink (print)); + } + } + + for (i = 0; i < FOCALTECH_MOC_MAX_FINGERS; i++) + { + struct EnrolledInfoItem *item = &data->enrolled_info->items[i]; + + if (data->enrolled_info->actived[i] == 0) + memset (item, 0, sizeof (struct EnrolledInfoItem)); + } + + fpi_ssm_next_state (self->task_ssm); + } +} + +static void +focaltech_moc_enroll_wait_finger_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + uint8_t *finger_status = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + finger_status = (uint8_t *) (fp_cmd + 1); + + if (fp_cmd->code != 0x04) + { + fpi_ssm_jump_to_state (self->task_ssm, MOC_ENROLL_WAIT_FINGER_DELAY); + } + else + { + + if (*finger_status == 0x01) + fpi_ssm_jump_to_state (self->task_ssm, MOC_ENROLL_ENROLL_CAPTURE); + else + fpi_ssm_jump_to_state (self->task_ssm, MOC_ENROLL_WAIT_FINGER_DELAY); + } +} + +static void +focaltech_moc_enroll_wait_finger_delay (FpDevice *device, + void *user_data + ) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + + fpi_ssm_jump_to_state (self->task_ssm, MOC_ENROLL_WAIT_FINGER); +} + +static void +focaltech_moc_start_enroll_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + struct UserId *user_id = NULL; + FpPrint *print = NULL; + struct EnrolledInfoItem *item = NULL; + int index; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + user_id = (struct UserId *) (fp_cmd + 1); + + if (fp_cmd->code != 0x04) + { + if (fp_cmd->code == 0x05) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_FULL, + "device data full!!")); + } + else + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + return; + } + + if (focaltech_moc_get_enrolled_info_item (self, user_id->uid, &item, &index) == 0) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "uid error!!")); + } + else + { + FpActionData *data = fpi_ssm_get_data (self->task_ssm); + g_autofree gchar *userid_safe = NULL; + gsize userid_len; + uint8_t found = 0; + int i; + struct EnrolledInfoItem *free_item = NULL; + + for (i = 0; i < FOCALTECH_MOC_MAX_FINGERS; i++) + { + item = &data->enrolled_info->items[i]; + + if (data->enrolled_info->actived[i] == 0) + { + found = 1; + free_item = item; + break; + } + } + + if (found == 0) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "no uid slot!!")); + } + else + { + fpi_device_get_enroll_data (FP_DEVICE (self), &print); + fprint_set_uid (print, user_id->uid, sizeof (user_id->uid)); + userid_safe = fpi_print_generate_user_id (print); + userid_len = strlen (userid_safe); + userid_len = MIN (FOCALTECH_MOC_USER_ID_LENGTH, userid_len); + fp_info ("focaltechmoc user id: %s", userid_safe); + memcpy (free_item->uid, user_id->uid, FOCALTECH_MOC_UID_PREFIX_LENGTH); + memcpy (free_item->user_id, userid_safe, userid_len); + fpi_ssm_next_state (self->task_ssm); + } + } +} + +static void +focaltech_moc_enroll_capture_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + struct CaptureResult *capture_result = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + capture_result = (struct CaptureResult *) (fp_cmd + 1); + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + else + { + if (capture_result->error == ERROR_NONE) + { + self->num_frames += 1; + enroll_status_report (self, ENROLL_RSP_ENROLL_REPORT, self->num_frames, NULL); + fp_info ("focaltechmoc remain: %d", capture_result->remain); + } + else + { + enroll_status_report (self, ENROLL_RSP_RETRY, self->num_frames, NULL); + } + + if (self->num_frames == fp_device_get_nr_enroll_stages (FP_DEVICE (self))) + fpi_ssm_next_state (self->task_ssm); + else + fpi_ssm_jump_to_state (self->task_ssm, MOC_ENROLL_WAIT_FINGER); + } +} + +static void +focaltech_moc_set_enrolled_info_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +focaltech_moc_commit_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + else + { + fp_info ("focaltech_moc_commit_cb success"); + enroll_status_report (self, ENROLL_RSP_ENROLL_OK, self->num_frames, NULL); + fpi_ssm_next_state (self->task_ssm); + } +} + +static void +focaltech_enroll_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + guint8 *cmd_buf = NULL; + uint16_t cmd_len = 0; + uint16_t resp_len = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case MOC_ENROLL_GET_ENROLLED_INFO: + { + uint8_t data = 0x00; + cmd_len = sizeof (uint8_t); + resp_len = sizeof (struct EnrolledInfoItem) * FOCALTECH_MOC_MAX_FINGERS; + cmd_buf = focaltech_moc_compose_cmd (0xaf, &data, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_get_enrolled_info_cb); + break; + } + + case MOC_ENROLL_GET_ENROLLED_LIST: + { + cmd_len = 0; + resp_len = sizeof (struct UidList); + cmd_buf = focaltech_moc_compose_cmd (0xab, NULL, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_get_enrolled_list_cb); + break; + } + + case MOC_ENROLL_RELEASE_FINGER: + { + uint8_t d1 = 0x78; + cmd_len = sizeof (d1); + resp_len = 0; + cmd_buf = focaltech_moc_compose_cmd (0x82, &d1, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_release_finger); + break; + } + + case MOC_ENROLL_START_ENROLL: + cmd_len = 0; + resp_len = sizeof (struct UserId); + cmd_buf = focaltech_moc_compose_cmd (0xa9, NULL, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_start_enroll_cb); + break; + + + case MOC_ENROLL_WAIT_FINGER: + { + uint8_t data = 0x02; + cmd_len = sizeof (uint8_t); + resp_len = sizeof (uint8_t); + cmd_buf = focaltech_moc_compose_cmd (0x80, &data, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_enroll_wait_finger_cb); + break; + } + + case MOC_ENROLL_WAIT_FINGER_DELAY: + fpi_device_add_timeout (device, 50, + focaltech_moc_enroll_wait_finger_delay, + NULL, NULL); + break; + + case MOC_ENROLL_ENROLL_CAPTURE: + cmd_len = 0; + resp_len = sizeof (struct CaptureResult); + cmd_buf = focaltech_moc_compose_cmd (0xa6, NULL, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_enroll_capture_cb); + break; + + case MOC_ENROLL_SET_ENROLLED_INFO: + { + g_autofree struct EnrolledInfoSetData *set_data = NULL; + FpActionData *data = fpi_ssm_get_data (self->task_ssm); + + cmd_len = sizeof (struct EnrolledInfoSetData); + resp_len = 0; + set_data = (struct EnrolledInfoSetData *) g_malloc0 (cmd_len); + set_data->data = 0x01; + memcpy (&set_data->items[0], &data->enrolled_info->items[0], + FOCALTECH_MOC_MAX_FINGERS * sizeof (struct EnrolledInfoItem)); + cmd_buf = focaltech_moc_compose_cmd (0xaf, (const uint8_t *) set_data, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_set_enrolled_info_cb); + break; + } + + case MOC_ENROLL_COMMIT_RESULT: + { + FpPrint *print = NULL; + g_autoptr(GVariant) data = NULL; + g_autoptr(GVariant) user_id_var = NULL; + const guint8 *user_id; + gsize user_id_len = 0; + + fpi_device_get_enroll_data (FP_DEVICE (self), &print); + g_object_get (print, "fpi-data", &data, NULL); + + if (!g_variant_check_format_string (data, "(@ay)", FALSE)) + { + fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + + g_variant_get (data, + "(@ay)", + &user_id_var); + user_id = g_variant_get_fixed_array (user_id_var, &user_id_len, 1); + + cmd_len = user_id_len; + resp_len = 0; + cmd_buf = focaltech_moc_compose_cmd (0xa3, user_id, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_commit_cb); + break; + } + } +} + +static void +focaltech_moc_enroll (FpDevice *device) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + FpActionData *data = g_new0 (FpActionData, 1); + + data->enrolled_info = g_new0 (struct EnrolledInfo, 1); + data->list_result = g_ptr_array_new_with_free_func (g_object_unref); + + self->task_ssm = fpi_ssm_new (FP_DEVICE (self), + focaltech_enroll_run_state, + MOC_ENROLL_NUM_STATES); + fpi_ssm_set_data (self->task_ssm, data, (GDestroyNotify) fp_action_ssm_done_data_free); + fpi_ssm_start (self->task_ssm, task_ssm_done); +} + +static void +focaltech_moc_delete_cb (FpiDeviceFocaltechMoc *self, + uint8_t *buffer_in, + gsize length_in, + GError *error) +{ + FpCmd *fp_cmd = NULL; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_cmd = (FpCmd *) buffer_in; + + if (fp_cmd->code != 0x04) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Can't get response!!")); + } + else + { + FpActionData *data = fpi_ssm_get_data (self->task_ssm); + int ssm_state; + + if (self->delete_slot != -1) + { + fp_dbg ("delete slot %d", self->delete_slot); + data->enrolled_info->actived[self->delete_slot] = 0; + memset (&data->enrolled_info->items[self->delete_slot], 0, sizeof (struct EnrolledInfoItem)); + memset (&data->enrolled_info->user_id[self->delete_slot], 0, sizeof (struct UserId)); + memset (&data->enrolled_info->user_des[self->delete_slot], 0, sizeof (struct UserDes)); + } + + ssm_state = fpi_ssm_get_cur_state (self->task_ssm); + fpi_ssm_jump_to_state (self->task_ssm, ssm_state); + } +} + +enum delete_states { + MOC_DELETE_GET_ENROLLED_INFO, + MOC_DELETE_GET_ENROLLED_LIST, + MOC_DELETE_SET_ENROLLED_INFO, + MOC_DELETE_BY_UID, + MOC_DELETE_BY_USER_INFO, + MOC_DELETE_NUM_STATES, +}; + +static void +focaltech_delete_run_state (FpiSsm *ssm, FpDevice *device) +{ + guint8 *cmd_buf = NULL; + uint16_t cmd_len = 0; + uint16_t resp_len = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case MOC_DELETE_GET_ENROLLED_INFO: + { + uint8_t data = 0x00; + cmd_len = sizeof (uint8_t); + resp_len = sizeof (struct EnrolledInfoItem) * FOCALTECH_MOC_MAX_FINGERS; + cmd_buf = focaltech_moc_compose_cmd (0xaf, &data, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_get_enrolled_info_cb); + break; + } + + case MOC_DELETE_GET_ENROLLED_LIST: + { + cmd_len = 0; + resp_len = sizeof (struct UidList) + sizeof (struct UserId) * FOCALTECH_MOC_MAX_FINGERS; + cmd_buf = focaltech_moc_compose_cmd (0xab, NULL, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_get_enrolled_list_cb); + break; + } + + case MOC_DELETE_SET_ENROLLED_INFO: + { + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + g_autofree struct EnrolledInfoSetData *set_data = NULL; + FpActionData *data = fpi_ssm_get_data (self->task_ssm); + + cmd_len = sizeof (struct EnrolledInfoSetData); + resp_len = 0; + set_data = (struct EnrolledInfoSetData *) g_malloc0 (cmd_len); + set_data->data = 0x01; + memcpy (&set_data->items[0], &data->enrolled_info->items[0], FOCALTECH_MOC_MAX_FINGERS * sizeof (struct EnrolledInfoItem)); + cmd_buf = focaltech_moc_compose_cmd (0xaf, (const uint8_t *) set_data, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_set_enrolled_info_cb); + break; + } + + case MOC_DELETE_BY_UID: + { + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + FpPrint *print = NULL; + g_autoptr(GVariant) data = NULL; + g_autoptr(GVariant) user_id_var = NULL; + const guint8 *user_id; + gsize user_id_len = 0; + struct EnrolledInfoItem *item = NULL; + int index; + + self->delete_slot = -1; + fpi_device_get_delete_data (device, &print); + g_object_get (print, "fpi-data", &data, NULL); + + if (!g_variant_check_format_string (data, "(@ay)", FALSE)) + { + fpi_device_delete_complete (device, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + + g_variant_get (data, "(@ay)", &user_id_var); + user_id = g_variant_get_fixed_array (user_id_var, &user_id_len, 1); + + if (focaltech_moc_get_enrolled_info_item (self, (uint8_t *) user_id, &item, &index) == 0) + self->delete_slot = index; + + if (self->delete_slot != -1) + { + cmd_len = sizeof (struct UserId); + resp_len = 0; + cmd_buf = focaltech_moc_compose_cmd (0xa8, user_id, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_delete_cb); + } + else + { + fpi_ssm_next_state (self->task_ssm); + } + + break; + } + + case MOC_DELETE_BY_USER_INFO: + { + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + FpActionData *data = fpi_ssm_get_data (self->task_ssm); + FpPrint *print = NULL; + const guint8 *user_id; + const gchar *username; + uint8_t finger; + int i; + + self->delete_slot = -1; + fpi_device_get_delete_data (device, &print); + username = fp_print_get_username (print); + finger = fp_print_get_finger (print); + + for (i = 0; i < FOCALTECH_MOC_MAX_FINGERS; i++) + { + struct UserDes *user_des = &data->enrolled_info->user_des[i]; + + if (username == NULL) + continue; + + if (strncmp (user_des->username, username, FOCALTECH_MOC_USER_ID_LENGTH) != 0) + continue; + + if (finger != user_des->finger) + continue; + + self->delete_slot = i; + } + + if (self->delete_slot != -1) + { + user_id = (const guint8 *) &data->enrolled_info->user_id[self->delete_slot].uid[0]; + cmd_len = sizeof (struct UserId); + resp_len = 0; + cmd_buf = focaltech_moc_compose_cmd (0xa8, user_id, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_delete_cb); + } + else + { + fpi_device_delete_complete (FP_DEVICE (self), NULL); + fpi_ssm_next_state (self->task_ssm); + } + + break; + } + } +} + +static void +focaltech_moc_delete_print (FpDevice *device) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + FpActionData *data = g_new0 (FpActionData, 1); + + data->enrolled_info = g_new0 (struct EnrolledInfo, 1); + data->list_result = g_ptr_array_new_with_free_func (g_object_unref); + + self->task_ssm = fpi_ssm_new (device, + focaltech_delete_run_state, + MOC_DELETE_NUM_STATES); + fpi_ssm_set_data (self->task_ssm, data, (GDestroyNotify) fp_action_ssm_done_data_free); + fpi_ssm_start (self->task_ssm, task_ssm_done); +} + +enum moc_list_states { + MOC_LIST_GET_ENROLLED_INFO, + MOC_LIST_GET_ENROLLED_LIST, + MOC_LIST_REPORT, + MOC_LIST_NUM_STATES, +}; + +static void +focaltech_list_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + guint8 *cmd_buf = NULL; + uint16_t cmd_len = 0; + uint16_t resp_len = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case MOC_LIST_GET_ENROLLED_INFO: + { + uint8_t data = 0x00; + cmd_len = sizeof (uint8_t); + resp_len = sizeof (struct EnrolledInfoItem) * FOCALTECH_MOC_MAX_FINGERS; + cmd_buf = focaltech_moc_compose_cmd (0xaf, &data, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_get_enrolled_info_cb); + break; + } + + case MOC_LIST_GET_ENROLLED_LIST: + { + cmd_len = 0; + resp_len = sizeof (struct UidList) + sizeof (struct UserId) * FOCALTECH_MOC_MAX_FINGERS; + cmd_buf = focaltech_moc_compose_cmd (0xab, NULL, cmd_len); + focaltech_moc_get_cmd (device, cmd_buf, + sizeof (FpCmd) + cmd_len + sizeof (uint8_t), + sizeof (FpCmd) + resp_len + sizeof (uint8_t), + 1, + focaltech_moc_get_enrolled_list_cb); + break; + } + + case MOC_LIST_REPORT: + { + FpActionData *data = fpi_ssm_get_data (self->task_ssm); + fpi_device_list_complete (FP_DEVICE (self), g_steal_pointer (&data->list_result), NULL); + fpi_ssm_next_state (self->task_ssm); + break; + } + } +} + +static void +focaltech_moc_list (FpDevice *device) +{ + FpiDeviceFocaltechMoc *self = FPI_DEVICE_FOCALTECH_MOC (device); + FpActionData *data = g_new0 (FpActionData, 1); + + data->enrolled_info = g_new0 (struct EnrolledInfo, 1); + data->list_result = g_ptr_array_new_with_free_func (g_object_unref); + self->task_ssm = fpi_ssm_new (device, + focaltech_list_run_state, + MOC_LIST_NUM_STATES); + fpi_ssm_set_data (self->task_ssm, data, (GDestroyNotify) fp_action_ssm_done_data_free); + fpi_ssm_start (self->task_ssm, task_ssm_done); +} + +static void +fpi_device_focaltech_moc_init (FpiDeviceFocaltechMoc *self) +{ + G_DEBUG_HERE (); +} + +static void +fpi_device_focaltech_moc_class_init (FpiDeviceFocaltechMocClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = FOCALTECH_MOC_DRIVER_FULLNAME; + + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + dev_class->id_table = id_table; + dev_class->nr_enroll_stages = FOCALTECH_MOC_MAX_FINGERS; + dev_class->temp_hot_seconds = -1; + + dev_class->open = focaltech_moc_open; + dev_class->close = focaltech_moc_close; + dev_class->verify = focaltech_moc_identify; + dev_class->enroll = focaltech_moc_enroll; + dev_class->identify = focaltech_moc_identify; + dev_class->delete = focaltech_moc_delete_print; + dev_class->list = focaltech_moc_list; + + fpi_device_class_auto_initialize_features (dev_class); +} diff --git a/libfprint/drivers/focaltech_moc/focaltech_moc.h b/libfprint/drivers/focaltech_moc/focaltech_moc.h new file mode 100644 index 00000000..9fcbec5d --- /dev/null +++ b/libfprint/drivers/focaltech_moc/focaltech_moc.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 Focaltech Microelectronics + * + * 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 + */ + +#pragma once + +#include "fpi-device.h" +#include "fpi-ssm.h" +#include + +#include +#include + +G_DECLARE_FINAL_TYPE (FpiDeviceFocaltechMoc, fpi_device_focaltech_moc, FPI, DEVICE_FOCALTECH_MOC, FpDevice) + +#define FOCALTECH_MOC_DRIVER_FULLNAME "Focaltech MOC Sensors" + +#define FOCALTECH_MOC_CMD_TIMEOUT 1000 +#define FOCALTECH_MOC_MAX_FINGERS 10 +#define FOCALTECH_MOC_UID_PREFIX_LENGTH 8 +#define FOCALTECH_MOC_USER_ID_LENGTH 64 + +typedef void (*FocaltechCmdMsgCallback) (FpiDeviceFocaltechMoc *self, + GError *error); + +struct _FpiDeviceFocaltechMoc +{ + FpDevice parent; + FpiSsm *task_ssm; + FpiSsm *cmd_ssm; + FpiUsbTransfer *cmd_transfer; + gboolean cmd_cancelable; + gsize cmd_len_in; + int num_frames; + int delete_slot; + guint8 bulk_in_ep; + guint8 bulk_out_ep; +}; diff --git a/libfprint/meson.build b/libfprint/meson.build index c2ebd8c1..da8285e5 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -143,6 +143,8 @@ driver_sources = { [ 'drivers/fpcmoc/fpc.c' ], 'realtek' : [ 'drivers/realtek/realtek.c' ], + 'focaltech_moc' : + [ 'drivers/focaltech_moc/focaltech_moc.c' ], } helper_sources = { diff --git a/meson.build b/meson.build index 9fc10315..aeef6911 100644 --- a/meson.build +++ b/meson.build @@ -132,6 +132,7 @@ default_drivers = [ 'nb1010', 'fpcmoc', 'realtek', + 'focaltech_moc', # SPI 'elanspi', diff --git a/tests/focaltech_moc/custom.pcapng b/tests/focaltech_moc/custom.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..9bcd05c9177d2d98783707ae5447d980f8069bdf GIT binary patch literal 38864 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 J3+d1B^}nh?<5&Oy literal 0 HcmV?d00001 diff --git a/tests/focaltech_moc/custom.py b/tests/focaltech_moc/custom.py new file mode 100755 index 00000000..6a876c60 --- /dev/null +++ b/tests/focaltech_moc/custom.py @@ -0,0 +1,89 @@ +#!/usr/bin/python3 + +import traceback +import sys +import gi + +gi.require_version('FPrint', '2.0') +from gi.repository import FPrint, GLib + +# Exit with error on any exception, included those happening in async callbacks +sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1)) + +ctx = GLib.main_context_default() + +c = FPrint.Context() +c.enumerate() +devices = c.get_devices() + +d = devices[0] +del devices + +assert d.get_driver() == "focaltech_moc" +assert not d.has_feature(FPrint.DeviceFeature.CAPTURE) +assert d.has_feature(FPrint.DeviceFeature.IDENTIFY) +assert d.has_feature(FPrint.DeviceFeature.VERIFY) +assert not d.has_feature(FPrint.DeviceFeature.DUPLICATES_CHECK) +assert d.has_feature(FPrint.DeviceFeature.STORAGE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_LIST) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE) +assert not d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) + +d.open_sync() + +template = FPrint.Print.new(d) + +def enroll_progress(*args): + #assert d.get_finger_status() == FPrint.FingerStatusFlags.NEEDED + print("finger status: ", d.get_finger_status()) + print('enroll progress: ' + str(args)) + +def identify_done(dev, res): + global identified + identified = True + identify_match, identify_print = dev.identify_finish(res) + print('indentification_done: ', identify_match, identify_print) + assert identify_match.equal(identify_print) + +# List, enroll, list, verify, identify, delete +print("enrolling") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll done") + +print("listing") +stored = d.list_prints_sync() +print("listing done") +assert len(stored) == 1 +assert stored[0].equal(p) +print("verifying") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +verify_res, verify_print = d.verify_sync(p) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("verify done") +del p +assert verify_res == True + +identified = False +deserialized_prints = [] +for p in stored: + deserialized_prints.append(FPrint.Print.deserialize(p.serialize())) + assert deserialized_prints[-1].equal(p) +del stored + +print('async identifying') +d.identify(deserialized_prints, callback=identify_done) +del deserialized_prints + +while not identified: + ctx.iteration(True) + +print("deleting") +d.delete_print_sync(p) +print("delete done") + +d.close_sync() + +del d +del c diff --git a/tests/focaltech_moc/device b/tests/focaltech_moc/device new file mode 100644 index 00000000..093807d1 --- /dev/null +++ b/tests/focaltech_moc/device @@ -0,0 +1,385 @@ +P: /devices/pci0000:00/0000:00:1c.4/0000:0b:00.0/usb3/3-1/3-1.4 +N: bus/usb/003/006=1201100100000040082879D900020102030109022000010100A0320904000002DCA0B0000705020240000007058102400000 +E: DEVNAME=/dev/bus/usb/003/006 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=2808/d979/200 +E: TYPE=0/0/0 +E: BUSNUM=003 +E: DEVNUM=006 +E: MAJOR=189 +E: MINOR=261 +E: SUBSYSTEM=usb +E: ID_VENDOR=CCore +E: ID_VENDOR_ENC=CCore +E: ID_VENDOR_ID=2808 +E: ID_MODEL=FocalTech_FT9349_ESS +E: ID_MODEL_ENC=FocalTech\x20FT9349\x20ESS +E: ID_MODEL_ID=d979 +E: ID_REVISION=0200 +E: ID_SERIAL=CCore_FocalTech_FT9349_ESS_1234567890ABCDEF +E: ID_SERIAL_SHORT=1234567890ABCDEF +E: ID_BUS=usb +E: ID_USB_INTERFACES=:dca0b0: +E: ID_PATH=pci-0000:0b:00.0-usb-0:1.4 +E: ID_PATH_TAG=pci-0000_0b_00_0-usb-0_1_4 +A: authorized=1 +A: avoid_reset_quirk=0 +A: bConfigurationValue=1 +A: bDeviceClass=00 +A: bDeviceProtocol=00 +A: bDeviceSubClass=00 +A: bMaxPacketSize0=64 +A: bMaxPower=100mA +A: bNumConfigurations=1 +A: bNumInterfaces= 1 +A: bcdDevice=0200 +A: bmAttributes=a0 +A: busnum=3 +A: configuration= +H: descriptors=1201100100000040082879D900020102030109022000010100A0320904000002DCA0B0000705020240000007058102400000 +A: dev=189:261 +A: devnum=6 +A: devpath=1.4 +L: driver=../../../../../../../bus/usb/drivers/usb +A: idProduct=d979 +A: idVendor=2808 +A: ltm_capable=no +A: manufacturer=CCore +A: maxchild=0 +L: port=../3-1:1.0/3-1-port4 +A: power/active_duration=130884 +A: power/async=enabled +A: power/autosuspend=2 +A: power/autosuspend_delay_ms=2000 +A: power/connected_duration=2778952 +A: power/control=auto +A: power/level=auto +A: power/persist=0 +A: power/runtime_active_kids=0 +A: power/runtime_active_time=131747 +A: power/runtime_enabled=enabled +A: power/runtime_status=active +A: power/runtime_suspended_time=2647026 +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: product=FocalTech FT9349 ESS +A: quirks=0x0 +A: removable=unknown +A: rx_lanes=1 +A: serial=1234567890ABCDEF +A: speed=12 +A: tx_lanes=1 +A: urbnum=1922 +A: version= 1.10 + +P: /devices/pci0000:00/0000:00:1c.4/0000:0b:00.0/usb3/3-1 +N: bus/usb/003/002=1201000209000140E305080636850001000109021900010100E0320904000001090000000705810301000C +E: DEVNAME=/dev/bus/usb/003/002 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=5e3/608/8536 +E: TYPE=9/0/1 +E: BUSNUM=003 +E: DEVNUM=002 +E: MAJOR=189 +E: MINOR=257 +E: SUBSYSTEM=usb +E: ID_VENDOR=05e3 +E: ID_VENDOR_ENC=05e3 +E: ID_VENDOR_ID=05e3 +E: ID_MODEL=USB2.0_Hub +E: ID_MODEL_ENC=USB2.0\x20Hub +E: ID_MODEL_ID=0608 +E: ID_REVISION=8536 +E: ID_SERIAL=05e3_USB2.0_Hub +E: ID_BUS=usb +E: ID_USB_INTERFACES=:090000: +E: ID_VENDOR_FROM_DATABASE=Genesys Logic, Inc. +E: ID_MODEL_FROM_DATABASE=Hub +E: ID_PATH=pci-0000:0b:00.0-usb-0:1 +E: ID_PATH_TAG=pci-0000_0b_00_0-usb-0_1 +E: ID_FOR_SEAT=usb-pci-0000_0b_00_0-usb-0_1 +E: TAGS=:seat: +A: authorized=1 +A: avoid_reset_quirk=0 +A: bConfigurationValue=1 +A: bDeviceClass=09 +A: bDeviceProtocol=01 +A: bDeviceSubClass=00 +A: bMaxPacketSize0=64 +A: bMaxPower=100mA +A: bNumConfigurations=1 +A: bNumInterfaces= 1 +A: bcdDevice=8536 +A: bmAttributes=e0 +A: busnum=3 +A: configuration= +H: descriptors=1201000209000140E305080636850001000109021900010100E0320904000001090000000705810301000C +A: dev=189:257 +A: devnum=2 +A: devpath=1 +L: driver=../../../../../../bus/usb/drivers/usb +A: idProduct=0608 +A: idVendor=05e3 +A: ltm_capable=no +A: maxchild=4 +L: port=../3-0:1.0/usb3-port1 +A: power/active_duration=6193132 +A: power/async=enabled +A: power/autosuspend=0 +A: power/autosuspend_delay_ms=0 +A: power/connected_duration=6193132 +A: power/control=auto +A: power/level=auto +A: power/runtime_active_kids=3 +A: power/runtime_active_time=6192633 +A: power/runtime_enabled=enabled +A: power/runtime_status=active +A: power/runtime_suspended_time=0 +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: product=USB2.0 Hub +A: quirks=0x0 +A: removable=unknown +A: rx_lanes=1 +A: speed=480 +A: tx_lanes=1 +A: urbnum=619 +A: version= 2.00 + +P: /devices/pci0000:00/0000:00:1c.4/0000:0b:00.0/usb3 +N: bus/usb/003/001=12010002090001406B1D020015050302010109021900010100E0000904000001090000000705810304000C +E: DEVNAME=/dev/bus/usb/003/001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=1d6b/2/515 +E: TYPE=9/0/1 +E: BUSNUM=003 +E: DEVNUM=001 +E: MAJOR=189 +E: MINOR=256 +E: SUBSYSTEM=usb +E: ID_VENDOR=Linux_5.15.0-52-generic_xhci-hcd +E: ID_VENDOR_ENC=Linux\x205.15.0-52-generic\x20xhci-hcd +E: ID_VENDOR_ID=1d6b +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_ID=0002 +E: ID_REVISION=0515 +E: ID_SERIAL=Linux_5.15.0-52-generic_xhci-hcd_xHCI_Host_Controller_0000:0b:00.0 +E: ID_SERIAL_SHORT=0000:0b:00.0 +E: ID_BUS=usb +E: ID_USB_INTERFACES=:090000: +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_PATH=pci-0000:0b:00.0 +E: ID_PATH_TAG=pci-0000_0b_00_0 +E: ID_FOR_SEAT=usb-pci-0000_0b_00_0 +E: TAGS=:seat: +A: authorized=1 +A: authorized_default=1 +A: avoid_reset_quirk=0 +A: bConfigurationValue=1 +A: bDeviceClass=09 +A: bDeviceProtocol=01 +A: bDeviceSubClass=00 +A: bMaxPacketSize0=64 +A: bMaxPower=0mA +A: bNumConfigurations=1 +A: bNumInterfaces= 1 +A: bcdDevice=0515 +A: bmAttributes=e0 +A: busnum=3 +A: configuration= +H: descriptors=12010002090001406B1D020015050302010109021900010100E0000904000001090000000705810304000C +A: dev=189:256 +A: devnum=1 +A: devpath=0 +L: driver=../../../../../bus/usb/drivers/usb +A: idProduct=0002 +A: idVendor=1d6b +A: interface_authorized_default=1 +A: ltm_capable=no +A: manufacturer=Linux 5.15.0-52-generic xhci-hcd +A: maxchild=2 +A: power/active_duration=6193348 +A: power/async=enabled +A: power/autosuspend=0 +A: power/autosuspend_delay_ms=0 +A: power/connected_duration=6193348 +A: power/control=auto +A: power/level=auto +A: power/runtime_active_kids=1 +A: power/runtime_active_time=6193145 +A: power/runtime_enabled=enabled +A: power/runtime_status=active +A: power/runtime_suspended_time=0 +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: product=xHCI Host Controller +A: quirks=0x0 +A: removable=unknown +A: rx_lanes=1 +A: serial=0000:0b:00.0 +A: speed=480 +A: tx_lanes=1 +A: urbnum=36 +A: version= 2.00 + +P: /devices/pci0000:00/0000:00:1c.4/0000:0b:00.0 +E: DRIVER=xhci_hcd +E: PCI_CLASS=C0330 +E: PCI_ID=104C:8241 +E: PCI_SUBSYS_ID=1028:050F +E: PCI_SLOT_NAME=0000:0b:00.0 +E: MODALIAS=pci:v0000104Cd00008241sv00001028sd0000050Fbc0Csc03i30 +E: SUBSYSTEM=pci +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_VENDOR_FROM_DATABASE=Texas Instruments +E: ID_MODEL_FROM_DATABASE=TUSB73x0 SuperSpeed USB 3.0 xHCI Host Controller +A: aer_dev_correctable=RxErr 0\nBadTLP 0\nBadDLLP 0\nRollover 0\nTimeout 0\nNonFatalErr 0\nCorrIntErr 0\nHeaderOF 0\nTOTAL_ERR_COR 0 +A: aer_dev_fatal=Undefined 0\nDLP 0\nSDES 0\nTLP 0\nFCP 0\nCmpltTO 0\nCmpltAbrt 0\nUnxCmplt 0\nRxOF 0\nMalfTLP 0\nECRC 0\nUnsupReq 0\nACSViol 0\nUncorrIntErr 0\nBlockedTLP 0\nAtomicOpBlocked 0\nTLPBlockedErr 0\nPoisonTLPBlocked 0\nTOTAL_ERR_FATAL 0 +A: aer_dev_nonfatal=Undefined 0\nDLP 0\nSDES 0\nTLP 0\nFCP 0\nCmpltTO 0\nCmpltAbrt 0\nUnxCmplt 0\nRxOF 0\nMalfTLP 0\nECRC 0\nUnsupReq 0\nACSViol 0\nUncorrIntErr 0\nBlockedTLP 0\nAtomicOpBlocked 0\nTLPBlockedErr 0\nPoisonTLPBlocked 0\nTOTAL_ERR_NONFATAL 0 +A: ari_enabled=0 +A: broken_parity_status=0 +A: class=0x0c0330 +H: config=4C104182060410000230030C100000000400D0F7000000000400D1F70000000000000000000000000000000028100F050000000040000000000000000B010000014883FE080000000570860000000000000000000000000000000000000000003020000000000000000000000000000010C00200C38F900500201900123C07004200121000000000000000000000000000000000100000000000000000000000020000000000000000000000000000000000000000000F0000000000000000001100078002000000021000000000000028100F05AB0D00001B0000003F000000000040CB00000000000000000000000000000000000000000000000000000000010002150000000000000000302046000020000000200000A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030001000000200000280008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003B00B100FFFFFFFF04000007000F0F1B2001010000000000AA430000800200000000000000000000117E7C031000000830C0000001800000FFFF0F00000000000F00000000000000855023000B50230000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F010000000000000000000000000000000000001B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +A: consistent_dma_mask_bits=64 +A: current_link_speed=5.0 GT/s PCIe +A: current_link_width=1 +A: d3cold_allowed=1 +A: device=0x8241 +A: dma_mask_bits=64 +L: driver=../../../../bus/pci/drivers/xhci_hcd +A: driver_override=(null) +A: enable=1 +A: irq=16 +A: link/clkpm=0 +A: link/l0s_aspm=0 +A: link/l1_aspm=1 +A: local_cpulist=0-3 +A: local_cpus=f +A: max_link_speed=5.0 GT/s PCIe +A: max_link_width=1 +A: modalias=pci:v0000104Cd00008241sv00001028sd0000050Fbc0Csc03i30 +A: msi_bus=1 +A: msi_irqs/25=msix +A: msi_irqs/26=msix +A: msi_irqs/27=msix +A: msi_irqs/28=msix +A: msi_irqs/29=msix +A: numa_node=-1 +A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 11 12 2112 12\nxHCI ring segments 34 34 4096 34\nbuffer-2048 16 32 2048 16\nbuffer-512 0 0 512 0\nbuffer-128 26 32 128 1\nbuffer-32 0 0 32 0 +A: power/async=enabled +A: power/control=on +A: power/runtime_active_kids=1 +A: power/runtime_active_time=6193932 +A: power/runtime_enabled=forbidden +A: power/runtime_status=active +A: power/runtime_suspended_time=0 +A: power/runtime_usage=1 +A: power/wakeup=enabled +A: power/wakeup_abort_count=0 +A: power/wakeup_active=0 +A: power/wakeup_active_count=0 +A: power/wakeup_count=0 +A: power/wakeup_expire_count=0 +A: power/wakeup_last_time_ms=0 +A: power/wakeup_max_time_ms=0 +A: power/wakeup_total_time_ms=0 +A: power_state=D0 +A: reset_method=bus +A: resource=0x00000000f7d00000 0x00000000f7d0ffff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x00000000f7d10000 0x00000000f7d11fff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000 +A: revision=0x02 +A: subsystem_device=0x050f +A: subsystem_vendor=0x1028 +A: vendor=0x104c + +P: /devices/pci0000:00/0000:00:1c.4 +E: DRIVER=pcieport +E: PCI_CLASS=60400 +E: PCI_ID=8086:1C18 +E: PCI_SUBSYS_ID=1028:050F +E: PCI_SLOT_NAME=0000:00:1c.4 +E: MODALIAS=pci:v00008086d00001C18sv00001028sd0000050Fbc06sc04i00 +E: SUBSYSTEM=pci +E: ID_PCI_CLASS_FROM_DATABASE=Bridge +E: ID_PCI_SUBCLASS_FROM_DATABASE=PCI bridge +E: ID_PCI_INTERFACE_FROM_DATABASE=Normal decode +E: ID_VENDOR_FROM_DATABASE=Intel Corporation +E: ID_MODEL_FROM_DATABASE=6 Series/C200 Series Chipset Family PCI Express Root Port 5 +A: ari_enabled=0 +A: broken_parity_status=0 +A: class=0x060400 +H: config=8680181C07001000B5000406100081000000000000000000000B0C00F0000020D0F7D0F7F1FF010000000000000000000000000040000000000000000B011200108042010080000000001000123C1205420012F000B2240000004001000000000000000016000000000000000000000002000000000000000000000000000000059000000000000000000000000000000DA0000028100F050000000000000000010002C8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001020B00000080118100000000003F00000000000001000000000000000000000000000000870F050800000000000000000000000000000000110006000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B363A7400001414311742005B6009002020000A521498095104690616000028BCB5BC4A00000000744C8500DC08DC0061091100D30F07005000E2005B00170001009400370494000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +A: consistent_dma_mask_bits=32 +A: current_link_speed=5.0 GT/s PCIe +A: current_link_width=1 +A: d3cold_allowed=1 +A: device=0x1c18 +A: dma_mask_bits=32 +L: driver=../../../bus/pci/drivers/pcieport +A: driver_override=(null) +A: enable=2 +A: irq=16 +A: local_cpulist=0-3 +A: local_cpus=f +A: max_link_speed=5.0 GT/s PCIe +A: max_link_width=1 +A: modalias=pci:v00008086d00001C18sv00001028sd0000050Fbc06sc04i00 +A: msi_bus=1 +A: numa_node=-1 +A: power/async=enabled +A: power/control=on +A: power/runtime_active_kids=1 +A: power/runtime_active_time=6193944 +A: power/runtime_enabled=forbidden +A: power/runtime_status=active +A: power/runtime_suspended_time=0 +A: power/runtime_usage=2 +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: power_state=D0 +A: reset_method=pm +A: resource=0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x00000000f7d00000 0x00000000f7dfffff 0x0000000000000200\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000 +A: revision=0xb5 +A: secondary_bus_number=11 +A: subordinate_bus_number=12 +A: subsystem_device=0x050f +A: subsystem_vendor=0x1028 +A: vendor=0x8086 + diff --git a/tests/meson.build b/tests/meson.build index f4f15979..48c83037 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -53,6 +53,7 @@ drivers_tests = [ 'egis0570', 'fpcmoc', 'realtek', + 'focaltech_moc', ] if get_option('introspection') From b8933d8f81661b9c1b47bce31ffb7c41b2ae6659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 14:34:54 +0100 Subject: [PATCH 27/79] fp-print: Do not check compile-time macros at runtime --- libfprint/fp-print.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libfprint/fp-print.c b/libfprint/fp-print.c index 7bcb9d8d..92323160 100644 --- a/libfprint/fp-print.c +++ b/libfprint/fp-print.c @@ -721,13 +721,12 @@ fp_print_serialize (FpPrint *print, result = g_variant_builder_end (&builder); - if (G_BYTE_ORDER == G_BIG_ENDIAN) - { - GVariant *tmp; - tmp = g_variant_byteswap (result); - g_variant_unref (result); - result = tmp; - } +#if (G_BYTE_ORDER == G_BIG_ENDIAN) + GVariant *tmp; + tmp = g_variant_byteswap (result); + g_variant_unref (result); + result = tmp; +#endif len = g_variant_get_size (result); /* Add 3 bytes of header */ @@ -800,10 +799,11 @@ fp_print_deserialize (const guchar *data, if (!raw_value) goto invalid_format; - if (G_BYTE_ORDER == G_BIG_ENDIAN) - value = g_variant_byteswap (raw_value); - else - value = g_variant_get_normal_form (raw_value); +#if (G_BYTE_ORDER == G_BIG_ENDIAN) + value = g_variant_byteswap (raw_value); +#else + value = g_variant_get_normal_form (raw_value); +#endif g_variant_get (value, "(i&s&sbymsmsi@a{sv}v)", From 85ec9ec5b222559f13ca34b850226294bb209a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 14:42:18 +0100 Subject: [PATCH 28/79] ci: Allow flatpak failure It's not a critical thing, so if it fails (as it does currently) we should not block on that. --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8bea6726..a5771e9e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -193,8 +193,10 @@ flatpak: - if: '$CI_PIPELINE_SOURCE == "schedule"' when: never - if: '$CI_COMMIT_BRANCH == "master"' + allow_failure: true when: always - if: '$CI_COMMIT_TAG' + allow_failure: true when: always # For any other (commit), allow manual run. # This excludes MRs which would create a duplicate pipeline From c64fa9c81b4d0b0ceda79c3ff27c59c47ce669ba Mon Sep 17 00:00:00 2001 From: Puspendu Banerjee Date: Mon, 12 Feb 2024 19:50:50 -0600 Subject: [PATCH 29/79] synaptics: Add new PID 0x0124 --- data/autosuspend.hwdb | 1 + libfprint/drivers/synaptics/synaptics.c | 1 + 2 files changed, 2 insertions(+) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 44dbbcd3..366f286c 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -224,6 +224,7 @@ usb:v06CBp0100* usb:v06CBp00F0* usb:v06CBp0103* usb:v06CBp0123* +usb:v06CBp0124* usb:v06CBp0126* usb:v06CBp0129* usb:v06CBp0168* diff --git a/libfprint/drivers/synaptics/synaptics.c b/libfprint/drivers/synaptics/synaptics.c index 55e4d44c..138e734d 100644 --- a/libfprint/drivers/synaptics/synaptics.c +++ b/libfprint/drivers/synaptics/synaptics.c @@ -40,6 +40,7 @@ static const FpIdEntry id_table[] = { { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x00F0, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0103, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0123, }, + { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0124, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0126, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0129, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0168, }, From 92c5fc464373d7b70600a22301b30701e76764a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 15:17:04 +0100 Subject: [PATCH 30/79] cleanup: Use allow/deny lists instead of color based ones There was nothing racist on those names here (what? Do human races even exist?!), but let's avoid any confusion. --- NEWS | 4 ++-- libfprint/fp-context.c | 20 ++++++++++---------- libfprint/fprint-list-udev-hwdb.c | 20 ++++++++++---------- tests/create-driver-test.py.in | 2 +- tests/egis0570/device | 2 +- tests/meson.build | 4 ++-- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/NEWS b/NEWS index 1b87665e..103740f6 100644 --- a/NEWS +++ b/NEWS @@ -357,7 +357,7 @@ tests of the drivers using umockdev. - Mark fp_dscv_print functions as deprecated * Udev rules: - - Add some unsupported devices to the whitelist + - Add some unsupported devices to the allowlist 2017-05-14: v0.7.0 release * Drivers: @@ -407,7 +407,7 @@ tests of the drivers using umockdev. - Fix possible race condition, and cancellation in uru4000 driver * Udev rules: - - Add Microsoft keyboard to the suspend blacklist + - Add Microsoft keyboard to the suspend denylist * Plenty of build fixes diff --git a/libfprint/fp-context.c b/libfprint/fp-context.c index 34fcdda8..f6c386df 100644 --- a/libfprint/fp-context.c +++ b/libfprint/fp-context.c @@ -67,29 +67,29 @@ enum { static guint signals[LAST_SIGNAL] = { 0 }; static const char * -get_drivers_whitelist_env (void) +get_drivers_allowlist_env (void) { - return g_getenv ("FP_DRIVERS_WHITELIST"); + return g_getenv ("FP_DRIVERS_ALLOWLIST"); } static gboolean is_driver_allowed (const gchar *driver) { - g_auto(GStrv) whitelisted_drivers = NULL; - const char *fp_drivers_whitelist_env; + g_auto(GStrv) allowlisted_drivers = NULL; + const char *fp_drivers_allowlist_env; int i; g_return_val_if_fail (driver, TRUE); - fp_drivers_whitelist_env = get_drivers_whitelist_env (); + fp_drivers_allowlist_env = get_drivers_allowlist_env (); - if (!fp_drivers_whitelist_env) + if (!fp_drivers_allowlist_env) return TRUE; - whitelisted_drivers = g_strsplit (fp_drivers_whitelist_env, ":", -1); + allowlisted_drivers = g_strsplit (fp_drivers_allowlist_env, ":", -1); - for (i = 0; whitelisted_drivers[i]; ++i) - if (g_strcmp0 (driver, whitelisted_drivers[i]) == 0) + for (i = 0; allowlisted_drivers[i]; ++i) + if (g_strcmp0 (driver, allowlisted_drivers[i]) == 0) return TRUE; return FALSE; @@ -364,7 +364,7 @@ fp_context_init (FpContext *self) priv->drivers = fpi_get_driver_types (); - if (get_drivers_whitelist_env ()) + if (get_drivers_allowlist_env ()) { for (i = 0; i < priv->drivers->len;) { diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index 7c41050b..3eca8040 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -24,7 +24,7 @@ #include "fpi-context.h" #include "fpi-device.h" -static const FpIdEntry whitelist_id_table[] = { +static const FpIdEntry allowlist_id_table[] = { /* Currently known and unsupported devices. * You can generate this list from the wiki page using e.g.: * gio cat https://gitlab.freedesktop.org/libfprint/wiki/-/wikis/Unsupported-Devices.md | sed -n 's!|.*\([0-9a-fA-F]\{4\}\):\([0-9a-fA-F]\{4\}\).*|.*! { .vid = 0x\1, .pid = 0x\2 },!p' @@ -138,18 +138,18 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0 }, }; -static const FpIdEntry blacklist_id_table[] = { +static const FpIdEntry denylist_id_table[] = { { .vid = 0x0483, .pid = 0x2016 }, /* https://bugs.freedesktop.org/show_bug.cgi?id=66659 */ { .vid = 0x045e, .pid = 0x00bb }, { .vid = 0 }, }; -static const FpDeviceClass whitelist = { +static const FpDeviceClass allowlist = { .type = FP_DEVICE_TYPE_USB, - .id_table = whitelist_id_table, - .id = "whitelist", - .full_name = "Hardcoded whitelist" + .id_table = allowlist_id_table, + .id = "allowlist", + .full_name = "Hardcoded allowlist" }; GHashTable *printed = NULL; @@ -168,7 +168,7 @@ print_driver (const FpDeviceClass *cls) const FpIdEntry *bl_entry; char *key; - for (bl_entry = blacklist_id_table; bl_entry->vid != 0; bl_entry++) + for (bl_entry = denylist_id_table; bl_entry->vid != 0; bl_entry++) if (entry->vid == bl_entry->vid && entry->pid == bl_entry->pid) break; @@ -179,7 +179,7 @@ print_driver (const FpDeviceClass *cls) if (g_hash_table_lookup (printed, key) != NULL) { - if (cls == &whitelist) + if (cls == &allowlist) g_warning ("%s implemented by driver %s", key, (const char *) g_hash_table_lookup (printed, key)); g_free (key); @@ -190,7 +190,7 @@ print_driver (const FpDeviceClass *cls) if (num_printed == 0) { - if (cls != &whitelist) + if (cls != &allowlist) g_print ("\n# Supported by libfprint driver %s\n", cls->id); else g_print ("\n# Known unsupported devices\n"); @@ -244,7 +244,7 @@ main (int argc, char **argv) print_driver (cls); } - print_driver (&whitelist); + print_driver (&allowlist); g_hash_table_destroy (printed); diff --git a/tests/create-driver-test.py.in b/tests/create-driver-test.py.in index 8173271f..64d96db4 100755 --- a/tests/create-driver-test.py.in +++ b/tests/create-driver-test.py.in @@ -44,7 +44,7 @@ if len(sys.argv) > 3: sys.exit(1) driver_name = sys.argv[1] -os.environ['FP_DRIVERS_WHITELIST'] = driver_name +os.environ['FP_DRIVERS_ALLOWLIST'] = driver_name test_variant = None if len(sys.argv) == 3: diff --git a/tests/egis0570/device b/tests/egis0570/device index 5247b785..3001bd98 100644 --- a/tests/egis0570/device +++ b/tests/egis0570/device @@ -24,7 +24,7 @@ E: ID_USB_INTERFACES=:ff0000: E: ID_VENDOR_FROM_DATABASE=LighTuning Technology Inc. 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=Hardcoded whitelist +E: LIBFPRINT_DRIVER=Hardcoded allowlist A: authorized=1\n A: avoid_reset_quirk=0\n A: bConfigurationValue=1\n diff --git a/tests/meson.build b/tests/meson.build index 48c83037..199500d2 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -15,7 +15,7 @@ envs.prepend('LD_LIBRARY_PATH', meson.project_build_root() / 'libfprint') envs.set('FP_DEVICE_EMULATION', '1') # Set a colon-separated list of native drivers we enable in tests -envs.set('FP_DRIVERS_WHITELIST', ':'.join([ +envs.set('FP_DRIVERS_ALLOWLIST', ':'.join([ 'virtual_image', 'virtual_device', 'virtual_device_storage', @@ -159,7 +159,7 @@ if get_option('introspection') foreach driver_test: drivers_tests driver_name = driver_test.split('-')[0] driver_envs = envs - driver_envs.set('FP_DRIVERS_WHITELIST', driver_name) + driver_envs.set('FP_DRIVERS_ALLOWLIST', driver_name) if (driver_name in supported_drivers and gusb_dep.version().version_compare('>= 0.3.0')) From f3ab1f996f0647ae516ad24fbb6caf8df6d7cd5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 15:23:43 +0100 Subject: [PATCH 31/79] fp-context: use g_strv_contains instead of manual labor --- libfprint/fp-context.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/libfprint/fp-context.c b/libfprint/fp-context.c index f6c386df..70d4062a 100644 --- a/libfprint/fp-context.c +++ b/libfprint/fp-context.c @@ -77,7 +77,6 @@ is_driver_allowed (const gchar *driver) { g_auto(GStrv) allowlisted_drivers = NULL; const char *fp_drivers_allowlist_env; - int i; g_return_val_if_fail (driver, TRUE); @@ -87,12 +86,7 @@ is_driver_allowed (const gchar *driver) return TRUE; allowlisted_drivers = g_strsplit (fp_drivers_allowlist_env, ":", -1); - - for (i = 0; allowlisted_drivers[i]; ++i) - if (g_strcmp0 (driver, allowlisted_drivers[i]) == 0) - return TRUE; - - return FALSE; + return g_strv_contains ((const gchar * const *) allowlisted_drivers, driver); } typedef struct From 2fa0975dec75960d5dae6ae3391475649b85a93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 15:17:57 +0100 Subject: [PATCH 32/79] cleanup: Address some newer uncrustify syntax cleanups --- libfprint/drivers/realtek/realtek.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libfprint/drivers/realtek/realtek.c b/libfprint/drivers/realtek/realtek.c index 87b8c805..4d734bd9 100644 --- a/libfprint/drivers/realtek/realtek.c +++ b/libfprint/drivers/realtek/realtek.c @@ -104,6 +104,7 @@ fp_finish_capture_cb (FpiDeviceRealtek *self, } gint capture_status = buffer_in[0]; + if (capture_status == 0) { fpi_device_report_finger_status_changes (FP_DEVICE (self), @@ -237,6 +238,7 @@ fp_identify_feature_cb (FpiDeviceRealtek *self, } gint in_status = buffer_in[0]; + if (in_status == FP_RTK_CMD_ERR) { fpi_ssm_mark_failed (self->task_ssm, @@ -424,6 +426,7 @@ fp_check_duplicate_cb (FpiDeviceRealtek *self, } gint in_status = buffer_in[0]; + if (in_status == FP_RTK_CMD_ERR) { fpi_ssm_mark_failed (self->task_ssm, From ee509c7ee67c512044f3f4c2d68fc7cf1316c200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 14:53:54 +0100 Subject: [PATCH 33/79] libfprint/fprint-list-udev-hwdb: Update unsupported list from wiki --- data/autosuspend.hwdb | 2 ++ libfprint/fprint-list-udev-hwdb.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 366f286c..215a96b3 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -317,6 +317,7 @@ usb:v04F3p3032* usb:v04F3p3057* usb:v04F3p3104* usb:v04F3p310D* +usb:v04F3p3128* usb:v06CBp0081* usb:v06CBp0088* usb:v06CBp008A* @@ -350,6 +351,7 @@ usb:v0BDAp5812* usb:v10A5p0007* usb:v10A5p9200* usb:v10A5p9800* +usb:v10A5pE340* usb:v1188p9545* usb:v138Ap0007* usb:v138Ap003A* diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index 3eca8040..28530a06 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -46,6 +46,7 @@ static const FpIdEntry allowlist_id_table[] = { { .vid = 0x04f3, .pid = 0x3057 }, { .vid = 0x04f3, .pid = 0x3104 }, { .vid = 0x04f3, .pid = 0x310d }, + { .vid = 0x04f3, .pid = 0x3128 }, { .vid = 0x06cb, .pid = 0x0081 }, { .vid = 0x06cb, .pid = 0x0088 }, { .vid = 0x06cb, .pid = 0x008a }, @@ -79,6 +80,7 @@ static const FpIdEntry allowlist_id_table[] = { { .vid = 0x10a5, .pid = 0x0007 }, { .vid = 0x10a5, .pid = 0x9200 }, { .vid = 0x10a5, .pid = 0x9800 }, + { .vid = 0x10a5, .pid = 0xe340 }, { .vid = 0x1188, .pid = 0x9545 }, { .vid = 0x138a, .pid = 0x0007 }, { .vid = 0x138a, .pid = 0x003a }, From d878148b5e641bc479491da6d15fdde2c711c855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 15:36:09 +0100 Subject: [PATCH 34/79] ci: Expose scan build artifacts on failures That's the only case we care about --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a5771e9e..71f55825 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -152,6 +152,7 @@ test_scan_build: - SCANBUILD=$CI_PROJECT_DIR/.gitlab-ci/scan-build ninja -C _build scan-build artifacts: + when: on_failure paths: - _build/meson-logs expire_in: 1 week From 61f9346aafd3f9a2904d6891e5f554b98694fb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 15:48:55 +0100 Subject: [PATCH 35/79] realtek: Do not leak an error Found by scan-build. --- libfprint/drivers/realtek/realtek.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libfprint/drivers/realtek/realtek.c b/libfprint/drivers/realtek/realtek.c index 4d734bd9..84bec5c5 100644 --- a/libfprint/drivers/realtek/realtek.c +++ b/libfprint/drivers/realtek/realtek.c @@ -249,9 +249,8 @@ fp_identify_feature_cb (FpiDeviceRealtek *self, if (in_status >= FP_RTK_TOO_HIGH && in_status <= FP_RTK_MERGE_FAILURE) { - GError *retry_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); - retry_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); - fpi_ssm_mark_failed (self->task_ssm, retry_error); + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); return; } From e05fbaa8ab124664e6df895e4dfbc084f3c0b223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 15:49:36 +0100 Subject: [PATCH 36/79] realtek: Do not initialize variables in switch cases Handles scan-build warning --- libfprint/drivers/realtek/realtek.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libfprint/drivers/realtek/realtek.c b/libfprint/drivers/realtek/realtek.c index 84bec5c5..45322908 100644 --- a/libfprint/drivers/realtek/realtek.c +++ b/libfprint/drivers/realtek/realtek.c @@ -864,13 +864,12 @@ fp_verify_sm_run_state (FpiSsm *ssm, FpDevice *device) static void fp_enroll_sm_run_state (FpiSsm *ssm, FpDevice *device) { + g_autofree gchar *user_id = NULL; + g_autofree guint8 *payload = NULL; FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); FpPrint *print = NULL; guint8 *cmd_buf = NULL; - - g_autofree gchar *user_id = NULL; - g_autofree guint8 *payload = NULL; - + guint8 *trans_id = NULL; GVariant *uid = NULL; GVariant *data = NULL; gsize user_id_len; @@ -920,8 +919,6 @@ fp_enroll_sm_run_state (FpiSsm *ssm, FpDevice *device) break; case FP_RTK_ENROLL_COMMIT: - guint8 *trans_id = NULL; - fpi_device_get_enroll_data (device, &print); user_id = fpi_print_generate_user_id (print); user_id_len = strlen (user_id); From 54ff730f0ca7283f1f6886703232f1ff77a8f635 Mon Sep 17 00:00:00 2001 From: Adam Jones Date: Thu, 4 Jan 2024 22:54:14 +0000 Subject: [PATCH 37/79] Fix typo of libfprint in supported devices list This also updates the title of the website page at https://fprint.freedesktop.org/supported-devices.html --- libfprint/fprint-list-supported-devices.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libfprint/fprint-list-supported-devices.c b/libfprint/fprint-list-supported-devices.c index ace5de09..1b02c0b2 100644 --- a/libfprint/fprint-list-supported-devices.c +++ b/libfprint/fprint-list-supported-devices.c @@ -110,7 +110,7 @@ main (int argc, char **argv) printed = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - g_print ("%% lifprint — Supported Devices\n"); + g_print ("%% libfprint — Supported Devices\n"); g_print ("%% Bastien Nocera, Daniel Drake\n"); g_print ("%% 2018\n"); g_print ("\n"); From 0b9a64331f01e16a13d6e47effeceb842014eec7 Mon Sep 17 00:00:00 2001 From: Joshua Grisham <18266314+joshuagrisham@users.noreply.github.com> Date: Sun, 8 Oct 2023 14:52:55 +0200 Subject: [PATCH 38/79] Initial commit of egismoc driver --- libfprint/drivers/egismoc/egismoc.c | 1443 +++++++++++++++++++++++++++ libfprint/drivers/egismoc/egismoc.h | 231 +++++ libfprint/meson.build | 2 + meson.build | 1 + tests/egismoc/custom.pcapng | Bin 0 -> 73752 bytes tests/egismoc/custom.py | 156 +++ tests/egismoc/device | 262 +++++ tests/meson.build | 1 + 8 files changed, 2096 insertions(+) create mode 100644 libfprint/drivers/egismoc/egismoc.c create mode 100644 libfprint/drivers/egismoc/egismoc.h create mode 100644 tests/egismoc/custom.pcapng create mode 100755 tests/egismoc/custom.py create mode 100644 tests/egismoc/device diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c new file mode 100644 index 00000000..0b249c44 --- /dev/null +++ b/libfprint/drivers/egismoc/egismoc.c @@ -0,0 +1,1443 @@ +/* + * Driver for Egis Technology (LighTuning) Match-On-Chip sensors + * Originally authored 2023 by Joshua Grisham + * + * Portions of code and logic inspired from the elanmoc libfprint driver + * which is copyright (C) 2021 Elan Microelectronics Inc (see elanmoc.c) + * + * Based on original reverse-engineering work by Joshua Grisham. The protocol has + * been reverse-engineered from captures of the official Windows driver, and by + * testing commands on the sensor with a multiplatform Python prototype driver: + * https://github.com/joshuagrisham/galaxy-book2-pro-linux/tree/main/fingerprint/ + * + * 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 + */ + +#define FP_COMPONENT "egismoc" + +#include +#include +#include + +#include "drivers_api.h" + +#include "egismoc.h" + +G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_DEVICE); + +static const FpIdEntry egismoc_id_table[] = { + { .vid = 0x1c7a, .pid = 0x0582 }, + { .vid = 0, .pid = 0 } +}; + +typedef void (*SynCmdMsgCallback) (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error); + +typedef struct egismoc_command_data +{ + SynCmdMsgCallback callback; +} CommandData; + +typedef struct egismoc_enroll_print +{ + FpPrint *print; + int stage; +} EnrollPrint; + +static void +egismoc_finger_on_sensor_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer userdata, + GError *error) +{ + fp_dbg ("Finger on sensor callback"); + fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); + + g_return_if_fail (transfer->ssm); + if (error) + fpi_ssm_mark_failed (transfer->ssm, error); + else + fpi_ssm_next_state (transfer->ssm); +} + +static void +egismoc_wait_finger_on_sensor (FpiSsm *ssm, + FpDevice *device) +{ + fp_dbg ("Wait for finger on sensor"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device); + + fpi_usb_transfer_fill_interrupt (transfer, EGISMOC_EP_CMD_INTERRUPT_IN, EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH); + transfer->ssm = ssm; + transfer->short_is_error = FALSE; /* Interrupt on this device always returns 1 byte short; this is expected */ + + fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED); + + fpi_usb_transfer_submit (g_steal_pointer (&transfer), + EGISMOC_USB_INTERRUPT_TIMEOUT, + self->interrupt_cancellable, + egismoc_finger_on_sensor_cb, + NULL); +} + +static gboolean +egismoc_validate_response_prefix (const guchar *buffer_in, + const gsize buffer_in_len, + const guchar *valid_prefix, + const gsize valid_prefix_len) +{ + const gboolean result = memcmp (buffer_in + (egismoc_read_prefix_len + EGISMOC_CHECK_BYTES_LENGTH), + valid_prefix, + valid_prefix_len) == 0; + + fp_dbg ("Response prefix valid: %s", result ? "yes" : "NO"); + return result; +} + +static gboolean +egismoc_validate_response_suffix (const guchar *buffer_in, + const gsize buffer_in_len, + const guchar *valid_suffix, + const gsize valid_suffix_len) +{ + const gboolean result = memcmp (buffer_in + (buffer_in_len - valid_suffix_len), + valid_suffix, + valid_suffix_len) == 0; + + fp_dbg ("Response suffix valid: %s", result ? "yes" : "NO"); + return result; +} + +static void +egismoc_task_ssm_done (FpiSsm *ssm, + FpDevice *device, + GError *error) +{ + fp_dbg ("Task SSM done"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + /* task_ssm already freed by completion of SSM */ + self->task_ssm = NULL; + + if (self->enrolled_ids) + g_ptr_array_free (self->enrolled_ids, TRUE); + self->enrolled_ids = NULL; + self->enrolled_num = -1; + + if (error) + fpi_device_action_error (device, error); +} + +static void +egismoc_task_ssm_next_state_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Task SSM next state callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + if (error) + fpi_ssm_mark_failed (self->task_ssm, error); + else + fpi_ssm_next_state (self->task_ssm); +} + +static void +egismoc_cmd_receive_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer userdata, + GError *error) +{ + fp_dbg ("Command receive callback"); + CommandData *data = userdata; + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + if (data == NULL || transfer->actual_length < egismoc_read_prefix_len) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + if (data->callback) + data->callback (device, transfer->buffer, transfer->actual_length, NULL); + + fpi_ssm_mark_completed (transfer->ssm); +} + +static void +egismoc_cmd_run_state (FpiSsm *ssm, + FpDevice *device) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + g_autoptr(FpiUsbTransfer) transfer = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case CMD_SEND: + if (self->cmd_transfer) + { + self->cmd_transfer->ssm = ssm; + fpi_usb_transfer_submit (g_steal_pointer (&self->cmd_transfer), + EGISMOC_USB_SEND_TIMEOUT, + NULL, + fpi_ssm_usb_transfer_cb, + NULL); + } + else + { + fpi_ssm_next_state (ssm); + } + break; + + case CMD_GET: + transfer = fpi_usb_transfer_new (device); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, EGISMOC_EP_CMD_IN, EGISMOC_USB_IN_RECV_LENGTH); + fpi_usb_transfer_submit (g_steal_pointer (&transfer), + EGISMOC_USB_RECV_TIMEOUT, + NULL, + egismoc_cmd_receive_cb, + fpi_ssm_get_data (ssm)); + break; + } +} + +static void +egismoc_cmd_ssm_done (FpiSsm *ssm, + FpDevice *device, + GError *error) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + CommandData *data = fpi_ssm_get_data (ssm); + + self->cmd_ssm = NULL; + self->cmd_transfer = NULL; + + if (error) + { + if (data->callback) + data->callback (device, NULL, 0, error); + else + g_error_free (error); + } +} + +typedef union egismoc_check_bytes +{ + unsigned short check_short; + guchar check_bytes[EGISMOC_CHECK_BYTES_LENGTH]; +} EgisMocCheckBytes; + +/* + Derive the 2 "check bytes" for write payloads + 32-bit big-endian sum of all 16-bit words (including check bytes) MOD 0xFFFF should be 0, otherwise + the device will reject the payload + */ +static EgisMocCheckBytes +egismoc_get_check_bytes (const guchar *value, + const gsize value_length) +{ + fp_dbg ("Get check bytes"); + EgisMocCheckBytes check_bytes; + unsigned short value_bigendian_shorts[(int) ((value_length + 1) / 2)]; + int s = 0; + + for (int i = 0; i < value_length; i = i + 2, s++) + { + if (i + 1 < value_length) + value_bigendian_shorts[s] = (((short) value[i + 1]) << 8) | (0x00ff & value[i]); + else + value_bigendian_shorts[s] = (((short) 0x00) << 8) | (0x00ff & value[i]); + } + unsigned long sum_shorts = 0; + + for (int i = 0; i < s; i++) + sum_shorts += value_bigendian_shorts[i]; + + /* + derive the "first possible occurence" of check bytes as: + `0xFFFF - (sum_of_32bit_words % 0xFFFF) + */ + check_bytes.check_short = 0xffff - (sum_shorts % 0xffff); + return check_bytes; +} + +static void +egismoc_exec_cmd (FpDevice *device, + guchar *cmd, + const gsize cmd_length, + GDestroyNotify cmd_destroy, + SynCmdMsgCallback callback) +{ + fp_dbg ("Execute command and get response"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + EgisMocCheckBytes check_bytes; + g_autofree guchar *buffer_out = NULL; + gsize buffer_out_length = 0; + + g_autoptr(FpiUsbTransfer) transfer = NULL; + CommandData *data = g_new0 (CommandData, 1); + + self->cmd_ssm = fpi_ssm_new (device, + egismoc_cmd_run_state, + CMD_STATES); + + transfer = fpi_usb_transfer_new (device); + transfer->short_is_error = TRUE; + + /* + buffer_out should be a fully composed command (with prefix, check bytes, etc) which looks like this + E G I S 00 00 00 01 {cb1} {cb2} {payload} + where cb1 and cb2 are some check bytes generated by the egismoc_get_check_bytes method + and payload is what is passed via the cmd parameter + */ + buffer_out_length = egismoc_write_prefix_len + + EGISMOC_CHECK_BYTES_LENGTH + + cmd_length; + buffer_out = g_malloc0 (buffer_out_length); + + /* Prefix */ + memcpy (buffer_out, egismoc_write_prefix, egismoc_write_prefix_len); + + /* Check Bytes - leave them as 00 for now then later generate and copy over the real ones */ + + /* Command Payload */ + memcpy (buffer_out + egismoc_write_prefix_len + EGISMOC_CHECK_BYTES_LENGTH, cmd, cmd_length); + + /* destroy cmd if requested */ + if (cmd_destroy) + cmd_destroy (cmd); + + /* Now fetch and set the "real" check bytes based on the currently assembled payload */ + check_bytes = egismoc_get_check_bytes (buffer_out, buffer_out_length); + memcpy (buffer_out + egismoc_write_prefix_len, check_bytes.check_bytes, EGISMOC_CHECK_BYTES_LENGTH); + + fpi_usb_transfer_fill_bulk_full (transfer, + EGISMOC_EP_CMD_OUT, + g_steal_pointer (&buffer_out), + buffer_out_length, + g_free); + transfer->ssm = self->cmd_ssm; + self->cmd_transfer = g_steal_pointer (&transfer); + data->callback = callback; + + fpi_ssm_set_data (self->cmd_ssm, data, g_free); + fpi_ssm_start (self->cmd_ssm, egismoc_cmd_ssm_done); +} + +static void +egismoc_set_print_data (FpPrint *print, + const guchar *device_print_id) +{ + g_autofree gchar *user_id = g_malloc (EGISMOC_FINGERPRINT_DATA_SIZE + 1); + GVariant *print_id_var = NULL; + GVariant *fpi_data = NULL; + + memcpy (user_id, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); + memset (user_id + EGISMOC_FINGERPRINT_DATA_SIZE, '\0', sizeof (gchar)); + + fpi_print_fill_from_user_id (print, user_id); + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + + g_object_set (print, "description", user_id, NULL); + + print_id_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + device_print_id, + EGISMOC_FINGERPRINT_DATA_SIZE, + sizeof (guchar)); + fpi_data = g_variant_new ("(@ay)", print_id_var); + g_object_set (print, "fpi-data", fpi_data, NULL); +} + +static GPtrArray * +egismoc_get_enrolled_prints (FpDevice *device) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + g_autoptr(GPtrArray) result = g_ptr_array_new_with_free_func (g_object_unref); + FpPrint *print = NULL; + + for (int i = 0; i < self->enrolled_num; i++) + { + print = fp_print_new (device); + egismoc_set_print_data (print, g_ptr_array_index (self->enrolled_ids, i)); + g_ptr_array_add (result, g_object_ref_sink (print)); + } + + return g_steal_pointer (&result); +} + +static void +egismoc_list_fill_enrolled_ids_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("List callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + if (self->enrolled_ids) + g_ptr_array_free (self->enrolled_ids, TRUE); + self->enrolled_ids = g_ptr_array_new_with_free_func (g_free); + self->enrolled_num = 0; + + /* + Each fingerprint ID will be returned in this response as a 32 byte array + The other stuff in the payload is 16 bytes long, so if there is at least 1 print + then the length should be at least 16+32=48 bytes long + */ + for (int pos = EGISMOC_LIST_RESPONSE_PREFIX_SIZE; + pos < length_in - EGISMOC_LIST_RESPONSE_SUFFIX_SIZE; + pos += EGISMOC_FINGERPRINT_DATA_SIZE, self->enrolled_num++) + { + g_autofree guchar *print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE); + memcpy (print_id, buffer_in + pos, EGISMOC_FINGERPRINT_DATA_SIZE); + fp_dbg ("Device fingerprint %0d: %.*s%c", self->enrolled_num, EGISMOC_FINGERPRINT_DATA_SIZE, print_id, '\0'); + g_ptr_array_add (self->enrolled_ids, g_steal_pointer (&print_id)); + } + + fp_info ("Number of currently enrolled fingerprints on the device is %d", self->enrolled_num); + + if (self->task_ssm) + fpi_ssm_next_state (self->task_ssm); +} + +static void +egismoc_list_run_state (FpiSsm *ssm, + FpDevice *device) +{ + g_autoptr(GPtrArray) enrolled_prints = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case LIST_GET_ENROLLED_IDS: + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + break; + + case LIST_RETURN_ENROLLED_PRINTS: + enrolled_prints = egismoc_get_enrolled_prints (device); + fpi_device_list_complete (device, g_steal_pointer (&enrolled_prints), NULL); + fpi_ssm_next_state (ssm); + break; + } +} + +static void +egismoc_list (FpDevice *device) +{ + fp_dbg ("List"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + self->task_ssm = fpi_ssm_new (device, + egismoc_list_run_state, + LIST_STATES); + fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); +} + +static guchar * +egismoc_get_delete_cmd (FpDevice *device, + FpPrint *delete_print, + gsize *length_out) +{ + fp_dbg ("Get delete command"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + g_autofree const gchar *print_description = NULL; + + g_autoptr(GVariant) print_data = NULL; + g_autoptr(GVariant) print_data_id_var = NULL; + const guchar *print_data_id = NULL; + gsize print_data_id_len = 0; + g_autofree guchar *enrolled_print_id = NULL; + guchar *result = NULL; + gsize pos = 0; + + /* + The final command body should contain: + 1) hard-coded 00 00 + 2) 2-byte size indiciator, 20*Number deleted identifiers plus 7 in form of: num_to_delete * 0x20 + 0x07 + Since max prints can be higher than 7 then this goes up to 2 bytes (e9 + 9 = 109) + 3) Hard-coded prefix (cmd_delete_prefix) + 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 7 (num_to_delete * 0x20) + 5) All of the currently registered prints to delete in their 32-byte device identifiers (enrolled_list) + */ + + const int num_to_delete = (!delete_print) ? self->enrolled_num : 1; + const gsize body_length = sizeof (guchar) * EGISMOC_FINGERPRINT_DATA_SIZE * num_to_delete; + /* total_length is the 6 various bytes plus prefix and body payload */ + const gsize total_length = (sizeof (guchar) * 6) + cmd_delete_prefix_len + body_length; + + /* pre-fill entire payload with 00s */ + result = g_malloc0 (total_length); + + /* start with 00 00 (just move starting offset up by 2) */ + pos = 2; + + /* Size Counter bytes */ + /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for when we go to the 2nd byte */ + /* note this will not work in case any model ever supports more than 14 prints (assumed max is 10) */ + if (num_to_delete > 7) + { + memset (result + pos, 0x01, sizeof (guchar)); + pos += sizeof (guchar); + memset (result + pos, ((num_to_delete - 8) * 0x20) + 0x07, sizeof (guchar)); + pos += sizeof (guchar); + } + else + { + /* first byte is 0x00, just skip it */ + pos += sizeof (guchar); + memset (result + pos, (num_to_delete * 0x20) + 0x07, sizeof (guchar)); + pos += sizeof (guchar); + } + + /* command prefix */ + memcpy (result + pos, cmd_delete_prefix, cmd_delete_prefix_len); + pos += cmd_delete_prefix_len; + + /* 2-bytes size logic for counter again */ + if (num_to_delete > 7) + { + memset (result + pos, 0x01, sizeof (guchar)); + pos += sizeof (guchar); + memset (result + pos, ((num_to_delete - 8) * 0x20), sizeof (guchar)); + pos += sizeof (guchar); + } + else + { + /* first byte is 0x00, just skip it */ + pos += sizeof (guchar); + memset (result + pos, (num_to_delete * 0x20), sizeof (guchar)); + pos += sizeof (guchar); + } + + /* append desired 32-byte fingerprint IDs */ + /* if passed a delete_print then fetch its data from the FpPrint */ + if (delete_print) + { + g_object_get (delete_print, "description", &print_description, NULL); + g_object_get (delete_print, "fpi-data", &print_data, NULL); + + if (!g_variant_check_format_string (print_data, "(@ay)", FALSE)) + { + fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return NULL; + } + + g_variant_get (print_data, "(@ay)", &print_data_id_var); + print_data_id = g_variant_get_fixed_array (print_data_id_var, &print_data_id_len, sizeof (guchar)); + + if (!g_str_has_prefix (print_description, "FP")) + fp_dbg ("Fingerprint '%s' was not created by libfprint; deleting anyway.", print_description); + + fp_info ("Delete fingerprint %s (%s)", print_description, print_data_id); + + memcpy (result + pos, print_data_id, EGISMOC_FINGERPRINT_DATA_SIZE); + } + /* Otherwise assume this is a "clear" - just loop through and append all enrolled IDs */ + else + { + for (int i = 0; i < self->enrolled_ids->len; i++) + memcpy (result + pos + (EGISMOC_FINGERPRINT_DATA_SIZE * i), + g_ptr_array_index (self->enrolled_ids, i), + EGISMOC_FINGERPRINT_DATA_SIZE); + } + pos += body_length; + + if (length_out) + *length_out = total_length; + + return g_steal_pointer (&result); +} + +static void +egismoc_delete_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Delete callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "success" with the delete */ + if (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_delete_success_prefix, + rsp_delete_success_prefix_len)) + { + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_CLEAR_STORAGE) + { + fpi_device_clear_storage_complete (device, NULL); + fpi_ssm_next_state (self->task_ssm); + } + else if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE) + { + fpi_device_delete_complete (device, NULL); + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Unsupported delete action.")); + } + } + else + { + fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Delete print was not successfull")); + } +} + +static void +egismoc_delete_run_state (FpiSsm *ssm, + FpDevice *device) +{ + g_autofree guchar *payload = NULL; + gsize payload_length = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case DELETE_GET_ENROLLED_IDS: + /* get enrolled_ids and enrolled_num from device for use building delete payload below */ + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + break; + + case DELETE_DELETE: + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE) + payload = egismoc_get_delete_cmd (device, fpi_ssm_get_data (ssm), &payload_length); + else + payload = egismoc_get_delete_cmd (device, NULL, &payload_length); + + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_delete_cb); + break; + } +} + +static void +egismoc_clear_storage (FpDevice *device) +{ + fp_dbg ("Clear storage"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + self->task_ssm = fpi_ssm_new (device, + egismoc_delete_run_state, + DELETE_STATES); + fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); +} + +static void +egismoc_delete (FpDevice *device) +{ + fp_dbg ("Delete"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + g_autoptr(FpPrint) delete_print = NULL; + + fpi_device_get_delete_data (device, &delete_print); + + self->task_ssm = fpi_ssm_new (device, + egismoc_delete_run_state, + DELETE_STATES); + fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&delete_print), NULL); /* todo leak or cleared by libfprint ? */ + fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); +} + +static void +egismoc_enroll_status_report (FpDevice *device, + EnrollPrint *enroll_print, + EnrollStatus status, + GError *error) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + switch (status) + { + case ENROLL_STATUS_DEVICE_FULL: + case ENROLL_STATUS_DUPLICATE: + fpi_ssm_mark_failed (self->task_ssm, error); + break; + + case ENROLL_STATUS_RETRY: + fpi_device_enroll_progress (device, enroll_print->stage, NULL, error); + break; + + case ENROLL_STATUS_PARTIAL_OK: + enroll_print->stage++; + fp_info ("Partial capture successful. Please touch the sensor again (%d/%d)", + enroll_print->stage, + EGISMOC_MAX_ENROLL_NUM); + fpi_device_enroll_progress (device, enroll_print->stage, enroll_print->print, NULL); + break; + + case ENROLL_STATUS_COMPLETE: + fp_info ("Enrollment was successful!"); + fpi_device_enroll_complete (device, g_object_ref (enroll_print->print), NULL); + break; + + default: + if (error) + fpi_ssm_mark_failed (self->task_ssm, error); + else + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Unknown error")); + } +} + +static void +egismoc_read_capture_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Read capture callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + EnrollPrint *enroll_print = fpi_ssm_get_data (self->task_ssm); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "success" */ + if (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_read_success_prefix, + rsp_read_success_prefix_len) && + egismoc_validate_response_suffix (buffer_in, + length_in, + rsp_read_success_suffix, + rsp_read_success_suffix_len)) + { + egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_PARTIAL_OK, NULL); + } + else + { + /* If not success then the sensor can either report "off center" or "sensor is dirty" */ + + /* "Off center" */ + if (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_read_offcenter_prefix, + rsp_read_offcenter_prefix_len) && + egismoc_validate_response_suffix (buffer_in, + length_in, + rsp_read_offcenter_suffix, + rsp_read_offcenter_suffix_len)) + error = fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER); + + /* "Sensor is dirty" */ + else if (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_read_dirty_prefix, + rsp_read_dirty_prefix_len)) + error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, + "Your device is having trouble recognizing you. Make sure your sensor is clean."); + + else + error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, + "Unknown failure trying to read your finger. Please try again."); + + egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_RETRY, error); + } + + if (enroll_print->stage == EGISMOC_ENROLL_TIMES) + fpi_ssm_next_state (self->task_ssm); + else + fpi_ssm_jump_to_state (self->task_ssm, ENROLL_CAPTURE_SENSOR_RESET); +} + +static void +egismoc_enroll_check_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Enroll check callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload reports "not yet enrolled" */ + if (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_check_not_yet_enrolled_prefix, + rsp_check_not_yet_enrolled_prefix_len)) + fpi_ssm_next_state (self->task_ssm); + else + egismoc_enroll_status_report (device, NULL, ENROLL_STATUS_DUPLICATE, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_DUPLICATE)); +} + +/* + Builds the full "check" payload which includes identifiers for all fingerprints which currently + should exist on the storage. This payload is used during both enrollment and verify actions. + */ +static guchar * +egismoc_get_check_cmd (FpDevice *device, + gsize *length_out) +{ + fp_dbg ("Get check command"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + guchar *device_print_id = NULL; + guchar *result = NULL; + gsize pos = 0; + + /* + The final command body should contain: + 1) hard-coded 00 00 + 2) 2-byte size indiciator, 20*Number enrolled identifiers plus 9 in form of: (enrolled_num + 1) * 0x20 + 0x09 + Since max prints can be higher than 7 then this goes up to 2 bytes (e9 + 9 = 109) + 3) Hard-coded prefix (cmd_check_prefix) + 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 9 ((enrolled_num + 1) * 0x20) + 5) Hard-coded 32 * 0x00 bytes + 6) All of the currently registered prints in their 32-byte device identifiers (enrolled_list) + 7) Hard-coded suffix (cmd_check_suffix) + */ + + const gsize body_length = sizeof (guchar) * self->enrolled_num * EGISMOC_FINGERPRINT_DATA_SIZE; + + /* total_length is the 6 various bytes plus all other prefixes/suffixes and the body payload */ + const gsize total_length = (sizeof (guchar) * 6) + + cmd_check_prefix_len + + EGISMOC_CMD_CHECK_SEPARATOR_LENGTH + + body_length + + cmd_check_suffix_len; + + /* pre-fill entire payload with 00s */ + result = g_malloc0 (total_length); + + /* start with 00 00 (just move starting offset up by 2) */ + pos = 2; + + /* Size Counter bytes */ + /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for when we go to the 2nd byte */ + /* note this will not work in case any model ever supports more than 14 prints (assumed max is 10) */ + if (self->enrolled_num > 6) + { + memset (result + pos, 0x01, sizeof (guchar)); + pos += sizeof (guchar); + memset (result + pos, ((self->enrolled_num - 7) * 0x20) + 0x09, sizeof (guchar)); + pos += sizeof (guchar); + } + else + { + /* first byte is 0x00, just skip it */ + pos += sizeof (guchar); + memset (result + pos, ((self->enrolled_num + 1) * 0x20) + 0x09, sizeof (guchar)); + pos += sizeof (guchar); + } + + /* command prefix */ + memcpy (result + pos, cmd_check_prefix, cmd_check_prefix_len); + pos += cmd_check_prefix_len; + + /* 2-bytes size logic for counter again */ + if (self->enrolled_num > 6) + { + memset (result + pos, 0x01, sizeof (guchar)); + pos += sizeof (guchar); + memset (result + pos, (self->enrolled_num - 7) * 0x20, sizeof (guchar)); + pos += sizeof (guchar); + } + else + { + /* first byte is 0x00, just skip it */ + pos += sizeof (guchar); + memset (result + pos, (self->enrolled_num + 1) * 0x20, sizeof (guchar)); + pos += sizeof (guchar); + } + + /* add 00s "separator" to offset position */ + pos += EGISMOC_CMD_CHECK_SEPARATOR_LENGTH; + + /* append all currently registered 32-byte fingerprint IDs */ + const gsize print_id_length = sizeof (guchar) * EGISMOC_FINGERPRINT_DATA_SIZE; + + for (int i = 0; i < self->enrolled_num; i++) + { + device_print_id = g_ptr_array_index (self->enrolled_ids, i); + memcpy (result + pos + (print_id_length * i), device_print_id, print_id_length); + } + pos += body_length; + + /* command suffix */ + memcpy (result + pos, cmd_check_suffix, cmd_check_suffix_len); + pos += cmd_check_suffix_len; + + if (length_out) + *length_out = total_length; + + return g_steal_pointer (&result); +} + +static void +egismoc_enroll_run_state (FpiSsm *ssm, + FpDevice *device) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + EnrollPrint *enroll_print = fpi_ssm_get_data (ssm); + g_autofree guchar *payload = NULL; + gsize payload_length = 0; + g_autofree guchar *device_print_id = NULL; + g_autofree gchar *user_id = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case ENROLL_GET_ENROLLED_IDS: + /* get enrolled_ids and enrolled_num from device for use in check stages below */ + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + break; + + case ENROLL_CHECK_ENROLLED_NUM: + if (self->enrolled_num >= EGISMOC_MAX_ENROLL_NUM) + { + egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_DEVICE_FULL, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_FULL)); + return; + } + fpi_ssm_next_state (ssm); + break; + + case ENROLL_SENSOR_RESET: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_SENSOR_ENROLL: + egismoc_exec_cmd (device, cmd_sensor_enroll, cmd_sensor_enroll_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_WAIT_FINGER: + egismoc_wait_finger_on_sensor (ssm, device); + break; + + case ENROLL_SENSOR_CHECK: + egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_CHECK: + payload = egismoc_get_check_cmd (device, &payload_length); + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_enroll_check_cb); + break; + + case ENROLL_START: + egismoc_exec_cmd (device, cmd_enroll_starting, cmd_enroll_starting_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_CAPTURE_SENSOR_RESET: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_CAPTURE_SENSOR_START_CAPTURE: + egismoc_exec_cmd (device, cmd_sensor_start_capture, cmd_sensor_start_capture_len, NULL, + egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_CAPTURE_WAIT_FINGER: + egismoc_wait_finger_on_sensor (ssm, device); + break; + + case ENROLL_CAPTURE_READ_RESPONSE: + egismoc_exec_cmd (device, cmd_read_capture, cmd_read_capture_len, NULL, egismoc_read_capture_cb); + break; + + case ENROLL_COMMIT_START: + egismoc_exec_cmd (device, cmd_commit_starting, cmd_commit_starting_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_COMMIT: + user_id = fpi_print_generate_user_id (enroll_print->print); + fp_dbg ("New fingerprint ID: %s", user_id); + + device_print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE); + memcpy (device_print_id, user_id, MIN (strlen (user_id), EGISMOC_FINGERPRINT_DATA_SIZE)); + egismoc_set_print_data (enroll_print->print, device_print_id); + + /* create new dynamic payload of cmd_new_print_prefix + device_print_id */ + payload_length = cmd_new_print_prefix_len + EGISMOC_FINGERPRINT_DATA_SIZE; + payload = g_malloc0 (payload_length); + memcpy (payload, cmd_new_print_prefix, cmd_new_print_prefix_len); + memcpy (payload + cmd_new_print_prefix_len, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); + + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_COMMIT_SENSOR_RESET: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_COMPLETE: + egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_COMPLETE, NULL); + fpi_ssm_next_state (ssm); + break; + } +} + +static void +egismoc_enroll (FpDevice *device) +{ + fp_dbg ("Enroll"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + EnrollPrint *enroll_print = g_new0 (EnrollPrint, 1); + + fpi_device_get_enroll_data (device, &enroll_print->print); + enroll_print->stage = 0; + + self->task_ssm = fpi_ssm_new (device, egismoc_enroll_run_state, ENROLL_STATES); + fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&enroll_print), g_free); + fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); +} + +static void +egismoc_identify_check_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Identify check callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + guchar device_print_id[EGISMOC_FINGERPRINT_DATA_SIZE]; + FpPrint *print = NULL; + FpPrint *verify_print = NULL; + GPtrArray *prints; + gboolean found = FALSE; + guint index; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "match" */ + if (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_identify_match_prefix, + rsp_identify_match_prefix_len) && + egismoc_validate_response_suffix (buffer_in, + length_in, + rsp_identify_match_suffix, + rsp_identify_match_suffix_len)) + { + /* + On success, there is a 32 byte array of "something"(?) in chars 14-45 + and then the 32 byte array ID of the matched print comes as chars 46-77 + */ + memcpy (device_print_id, + buffer_in + EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET, + EGISMOC_FINGERPRINT_DATA_SIZE); + + /* Create a new print from this ID and then see if it matches the one indicated */ + print = fp_print_new (device); + egismoc_set_print_data (print, device_print_id); + + if (!print) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "Failed to build a print from device response.")); + return; + } + + fp_info ("Identify successful for: %s", fp_print_get_description (print)); + + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY) + { + fpi_device_get_identify_data (device, &prints); + found = g_ptr_array_find_with_equal_func (prints, + print, + (GEqualFunc) fp_print_equal, + &index); + + if (found) + fpi_device_identify_report (device, g_ptr_array_index (prints, index), print, NULL); + else + fpi_device_identify_report (device, NULL, print, NULL); + + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_device_get_verify_data (device, &verify_print); + fp_info ("Verifying against: %s", fp_print_get_description (verify_print)); + + if (fp_print_equal (verify_print, print)) + fpi_device_verify_report (device, FPI_MATCH_SUCCESS, print, NULL); + else + fpi_device_verify_report (device, FPI_MATCH_FAIL, print, NULL); + + fpi_ssm_next_state (self->task_ssm); + } + } + /* If device was successfully read but it was a "not matched" */ + else if (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_identify_notmatch_prefix, + rsp_identify_notmatch_prefix_len)) + { + fp_info ("Print was not identified by the device"); + + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) + { + fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL); + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_device_identify_report (device, NULL, NULL, NULL); + fpi_ssm_next_state (self->task_ssm); + } + } + else + { + fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Unrecognized response from device.")); + } +} + +static void +egismoc_identify_run_state (FpiSsm *ssm, + FpDevice *device) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + g_autofree guchar *payload = NULL; + gsize payload_length = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case IDENTIFY_GET_ENROLLED_IDS: + /* get enrolled_ids and enrolled_num from device for use in check stages below */ + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + break; + + case IDENTIFY_CHECK_ENROLLED_NUM: + if (self->enrolled_num == 0) + { + fpi_ssm_mark_failed (g_steal_pointer (&self->task_ssm), + fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); + return; + } + fpi_ssm_next_state (ssm); + break; + + case IDENTIFY_SENSOR_RESET: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case IDENTIFY_SENSOR_IDENTIFY: + egismoc_exec_cmd (device, cmd_sensor_identify, cmd_sensor_identify_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case IDENTIFY_WAIT_FINGER: + egismoc_wait_finger_on_sensor (ssm, device); + break; + + case IDENTIFY_SENSOR_CHECK: + egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case IDENTIFY_CHECK: + payload = egismoc_get_check_cmd (device, &payload_length); + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_identify_check_cb); + break; + + case IDENTIFY_COMPLETE_SENSOR_RESET: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + /* + In Windows, the driver seems at this point to then immediately take another read from the sensor; + this is suspected to be an on-chip "verify". However, because the user's finger is still on the + sensor from the identify, then it seems to always return positive. We will consider this extra + step unnecessary and just skip it in this driver. This driver will instead handle matching of the + FpPrint from the gallery in the "verify" case of the callback egismoc_identify_check_cb. + */ + + case IDENTIFY_COMPLETE: + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY) + fpi_device_identify_complete (device, NULL); + else + fpi_device_verify_complete (device, NULL); + + fpi_ssm_mark_completed (ssm); + break; + } +} + +static void +egismoc_identify_verify (FpDevice *device) +{ + fp_dbg ("Identify or Verify"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + self->task_ssm = fpi_ssm_new (device, egismoc_identify_run_state, IDENTIFY_STATES); + fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); +} + +static void +egismoc_fw_version_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Firmware version callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + g_autofree guchar *fw_version = NULL; + gsize prefix_length; + gsize fw_version_length; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "success" */ + if (!egismoc_validate_response_suffix (buffer_in, + length_in, + rsp_fw_version_suffix, + rsp_fw_version_suffix_len)) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Device firmware response was not valid.")); + return; + } + + /* + FW Version is 12 bytes: a carriage return (0x0d) plus the version string itself. + Always skip [the read prefix] + [2 * check bytes] + [3 * 0x00] that come with every payload + Then we will also skip the carriage return and take all but the last 2 bytes as the FW Version + */ + prefix_length = egismoc_read_prefix_len + 2 + 3 + 1; + fw_version_length = length_in - prefix_length - rsp_fw_version_suffix_len; + fw_version = g_malloc0 (fw_version_length + 1); + + memcpy (fw_version, + buffer_in + prefix_length, + length_in - prefix_length - rsp_fw_version_suffix_len); + *(fw_version + fw_version_length) = '\0'; + + fp_info ("Device firmware version is %s", fw_version); + + fpi_ssm_next_state (self->task_ssm); +} + +static void +egismoc_dev_init_done (FpiSsm *ssm, + FpDevice *device, + GError *error) +{ + if (error) + g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, NULL); + + fpi_device_open_complete (device, error); +} + +static void +egismoc_dev_init_handler (FpiSsm *ssm, + FpDevice *device) +{ + g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case DEV_INIT_CONTROL1: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 32, 0x0000, 4, 16); + goto send_control; + break; + + case DEV_INIT_CONTROL2: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 32, 0x0000, 4, 40); + goto send_control; + break; + + case DEV_INIT_CONTROL3: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_STANDARD, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0, 0x0000, 0, 2); + goto send_control; + break; + + case DEV_INIT_CONTROL4: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_STANDARD, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0, 0x0000, 0, 2); + goto send_control; + break; + + case DEV_INIT_CONTROL5: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 82, 0x0000, 0, 8); + goto send_control; + break; + + case DEV_GET_FW_VERSION: + egismoc_exec_cmd (device, cmd_fw_version, cmd_fw_version_len, NULL, egismoc_fw_version_cb); + break; + } + + return; + +send_control: + transfer->ssm = ssm; + transfer->short_is_error = TRUE; + fpi_usb_transfer_submit (g_steal_pointer (&transfer), + EGISMOC_USB_CONTROL_TIMEOUT, + NULL, + fpi_ssm_usb_transfer_cb, + NULL); +} + +static void +egismoc_open (FpDevice *device) +{ + fp_dbg ("Opening device"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + GError *error = NULL; + + self->interrupt_cancellable = g_cancellable_new (); + + if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) + goto error; + + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error)) + goto error; + + self->task_ssm = fpi_ssm_new (device, egismoc_dev_init_handler, DEV_INIT_STATES); + fpi_ssm_start (self->task_ssm, egismoc_dev_init_done); + return; + +error: + return fpi_device_open_complete (device, error); +} + +static void +egismoc_cancel (FpDevice *device) +{ + fp_dbg ("Cancel"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + g_cancellable_cancel (self->interrupt_cancellable); + g_clear_object (&self->interrupt_cancellable); + self->interrupt_cancellable = g_cancellable_new (); +} + +static void +egismoc_close (FpDevice *device) +{ + fp_dbg ("Closing device"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + GError *error = NULL; + + egismoc_cancel (device); + + g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error); + fpi_device_close_complete (device, error); + + if (self->task_ssm) + fpi_ssm_free (self->task_ssm); + self->task_ssm = NULL; + + if (self->cmd_ssm) + fpi_ssm_free (self->cmd_ssm); + self->cmd_ssm = NULL; + + self->cmd_transfer = NULL; + + g_clear_object (&self->interrupt_cancellable); + + if (self->enrolled_ids) + g_ptr_array_free (self->enrolled_ids, TRUE); + self->enrolled_ids = NULL; + self->enrolled_num = -1; +} + +static void +fpi_device_egismoc_init (FpiDeviceEgisMoc *self) +{ + G_DEBUG_HERE (); +} + +static void +fpi_device_egismoc_class_init (FpiDeviceEgisMocClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = EGISMOC_DRIVER_FULLNAME; + + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + dev_class->id_table = egismoc_id_table; + dev_class->nr_enroll_stages = EGISMOC_ENROLL_TIMES; + dev_class->temp_hot_seconds = 0; /* device should be "always off" unless being used */ + + dev_class->open = egismoc_open; + dev_class->cancel = egismoc_cancel; + dev_class->suspend = egismoc_cancel; + dev_class->close = egismoc_close; + dev_class->identify = egismoc_identify_verify; + dev_class->verify = egismoc_identify_verify; + dev_class->enroll = egismoc_enroll; + dev_class->delete = egismoc_delete; + dev_class->clear_storage = egismoc_clear_storage; + dev_class->list = egismoc_list; + + fpi_device_class_auto_initialize_features (dev_class); + dev_class->features |= FP_DEVICE_FEATURE_DUPLICATES_CHECK; +} diff --git a/libfprint/drivers/egismoc/egismoc.h b/libfprint/drivers/egismoc/egismoc.h new file mode 100644 index 00000000..c30bac1b --- /dev/null +++ b/libfprint/drivers/egismoc/egismoc.h @@ -0,0 +1,231 @@ +/* + * Driver for Egis Technology (LighTuning) Match-On-Chip sensors + * Originally authored 2023 by Joshua Grisham + * + * Portions of code and logic inspired from the elanmoc libfprint driver + * which is copyright (C) 2021 Elan Microelectronics Inc (see elanmoc.c) + * + * Based on original reverse-engineering work by Joshua Grisham. The protocol has + * been reverse-engineered from captures of the official Windows driver, and by + * testing commands on the sensor with a multiplatform Python prototype driver: + * https://github.com/joshuagrisham/galaxy-book2-pro-linux/tree/main/fingerprint/ + * + * 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 + */ + +#pragma once + +#include "fpi-device.h" +#include "fpi-ssm.h" + +G_DECLARE_FINAL_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FPI, DEVICE_EGISMOC, FpDevice) + +#define EGISMOC_DRIVER_FULLNAME "Egis Technology (LighTuning) Match-on-Chip" +#define EGISMOC_EP_CMD_OUT (0x02 | FPI_USB_ENDPOINT_OUT) +#define EGISMOC_EP_CMD_IN (0x81 | FPI_USB_ENDPOINT_IN) +#define EGISMOC_EP_CMD_INTERRUPT_IN 0x83 + +#define EGISMOC_USB_CONTROL_TIMEOUT 5000 +#define EGISMOC_USB_SEND_TIMEOUT 5000 +#define EGISMOC_USB_RECV_TIMEOUT 5000 +#define EGISMOC_USB_INTERRUPT_TIMEOUT 0 + +#define EGISMOC_USB_IN_RECV_LENGTH 4096 +#define EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH 64 + +#define EGISMOC_ENROLL_TIMES 10 +#define EGISMOC_MAX_ENROLL_NUM 10 +#define EGISMOC_FINGERPRINT_DATA_SIZE 32 +#define EGISMOC_LIST_RESPONSE_PREFIX_SIZE 14 +#define EGISMOC_LIST_RESPONSE_SUFFIX_SIZE 2 + +struct _FpiDeviceEgisMoc +{ + FpDevice parent; + FpiSsm *task_ssm; + FpiSsm *cmd_ssm; + FpiUsbTransfer *cmd_transfer; + GCancellable *interrupt_cancellable; + + int enrolled_num; + GPtrArray *enrolled_ids; +}; + + +/* standard prefixes for all read/writes */ + +static guchar egismoc_write_prefix[] = {'E', 'G', 'I', 'S', 0x00, 0x00, 0x00, 0x01}; +static gsize egismoc_write_prefix_len = sizeof (egismoc_write_prefix) / sizeof (egismoc_write_prefix[0]); + +static guchar egismoc_read_prefix[] = {'S', 'I', 'G', 'E', 0x00, 0x00, 0x00, 0x01}; +static gsize egismoc_read_prefix_len = sizeof (egismoc_read_prefix) / sizeof (egismoc_read_prefix[0]); + + +/* hard-coded command payloads */ + +static guchar cmd_fw_version[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x0c}; +static gsize cmd_fw_version_len = sizeof (cmd_fw_version) / sizeof (cmd_fw_version[0]); +static guchar rsp_fw_version_suffix[] = {0x90, 0x00}; +static gsize rsp_fw_version_suffix_len = sizeof (rsp_fw_version_suffix) / sizeof (rsp_fw_version_suffix[0]); + +static guchar cmd_list[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x19, 0x04, 0x00, 0x00, 0x01, 0x40}; +static gsize cmd_list_len = sizeof (cmd_list) / sizeof (cmd_list[0]); + +static guchar cmd_sensor_reset[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x1a, 0x00, 0x00}; +static gsize cmd_sensor_reset_len = sizeof (cmd_sensor_reset) / sizeof (cmd_sensor_reset[0]); + +static guchar cmd_sensor_check[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x17, 0x02, 0x00}; +static gsize cmd_sensor_check_len = sizeof (cmd_sensor_check) / sizeof (cmd_sensor_check[0]); + +static guchar cmd_sensor_identify[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x17, 0x01, 0x01}; +static gsize cmd_sensor_identify_len = sizeof (cmd_sensor_identify) / sizeof (cmd_sensor_identify[0]); +static guchar rsp_identify_match_prefix[] = {0x00, 0x00, 0x00, 0x42}; +static gsize rsp_identify_match_prefix_len = sizeof (rsp_identify_match_prefix) / sizeof (rsp_identify_match_prefix[0]); +static guchar rsp_identify_match_suffix[] = {0x90, 0x00}; +static gsize rsp_identify_match_suffix_len = sizeof (rsp_identify_match_suffix) / sizeof (rsp_identify_match_suffix[0]); +static guchar rsp_identify_notmatch_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x90, 0x04}; +static gsize rsp_identify_notmatch_prefix_len = sizeof (rsp_identify_notmatch_prefix) / sizeof (rsp_identify_notmatch_prefix[0]); + +static guchar cmd_sensor_enroll[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x17, 0x01, 0x00}; +static gsize cmd_sensor_enroll_len = sizeof (cmd_sensor_enroll) / sizeof (cmd_sensor_enroll[0]); + +static guchar cmd_enroll_starting[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x16, 0x01, 0x00, 0x00, 0x00, 0x20}; +static gsize cmd_enroll_starting_len = sizeof (cmd_enroll_starting) / sizeof (cmd_enroll_starting[0]); + +static guchar cmd_sensor_start_capture[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x16, 0x02, 0x01}; +static gsize cmd_sensor_start_capture_len = sizeof (cmd_sensor_start_capture) / sizeof (cmd_sensor_start_capture[0]); + +static guchar cmd_read_capture[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x16, 0x02, 0x02, 0x00, 0x00, 0x02}; +static gsize cmd_read_capture_len = sizeof (cmd_read_capture) / sizeof (cmd_read_capture[0]); +static guchar rsp_read_success_prefix[] = {0x00, 0x00, 0x00, 0x04}; +static gsize rsp_read_success_prefix_len = sizeof (rsp_read_success_prefix) / sizeof (rsp_read_success_prefix[0]); +static guchar rsp_read_success_suffix[] = {0x0a, 0x90, 0x00}; +static gsize rsp_read_success_suffix_len = sizeof (rsp_read_success_suffix) / sizeof (rsp_read_success_suffix[0]); +static guchar rsp_read_offcenter_prefix[] = {0x00, 0x00, 0x00, 0x04}; +static gsize rsp_read_offcenter_prefix_len = sizeof (rsp_read_offcenter_prefix) / sizeof (rsp_read_offcenter_prefix[0]); +static guchar rsp_read_offcenter_suffix[] = {0x0a, 0x64, 0x91}; +static gsize rsp_read_offcenter_suffix_len = sizeof (rsp_read_offcenter_suffix) / sizeof (rsp_read_offcenter_suffix[0]); +static guchar rsp_read_dirty_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x64}; +static gsize rsp_read_dirty_prefix_len = sizeof (rsp_read_dirty_prefix) / sizeof (rsp_read_dirty_prefix[0]); + +static guchar cmd_commit_starting[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x16, 0x05, 0x00, 0x00, 0x00, 0x20}; +static gsize cmd_commit_starting_len = sizeof (cmd_commit_starting) / sizeof (cmd_commit_starting[0]); + + +/* commands which exist on the device but are currently not used */ +/* + static guchar cmd_sensor_cancel[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x16, 0x04, 0x00}; + static gsize cmd_sensor_cancel_len = sizeof(cmd_sensor_cancel) / sizeof(cmd_sensor_cancel[0]); + + static guchar cmd_sensor_verify[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x04, 0x01, 0x00}; + static gsize cmd_sensor_verify_len = sizeof(cmd_sensor_verify) / sizeof(cmd_sensor_verify[0]); + + static guchar cmd_read_verify[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x04, 0x02, 0x00}; + static gsize cmd_read_verify_len = sizeof(cmd_read_verify) / sizeof(cmd_read_verify[0]); + */ + + +/* prefixes/suffixes and other things for dynamically created command payloads */ + +#define EGISMOC_CHECK_BYTES_LENGTH 2 +#define EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET 46 +#define EGISMOC_CMD_CHECK_SEPARATOR_LENGTH 32 + +static guchar cmd_new_print_prefix[] = {0x00, 0x00, 0x00, 0x27, 0x50, 0x16, 0x03, 0x00, 0x00, 0x00, 0x20}; +static gsize cmd_new_print_prefix_len = sizeof (cmd_new_print_prefix) / sizeof (cmd_new_print_prefix[0]); + +static guchar cmd_delete_prefix[] = {0x50, 0x18, 0x04, 0x00, 0x00}; +static gsize cmd_delete_prefix_len = sizeof (cmd_delete_prefix) / sizeof (cmd_delete_prefix[0]); +static guchar rsp_delete_success_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x90, 0x00}; +static gsize rsp_delete_success_prefix_len = sizeof (rsp_delete_success_prefix) / sizeof (rsp_delete_success_prefix[0]); + +static guchar cmd_check_prefix[] = {0x50, 0x17, 0x03, 0x00, 0x00}; +static gsize cmd_check_prefix_len = sizeof (cmd_check_prefix) / sizeof (cmd_check_prefix[0]); +static guchar cmd_check_suffix[] = {0x00, 0x40}; +static gsize cmd_check_suffix_len = sizeof (cmd_check_suffix) / sizeof (cmd_check_suffix[0]); +static guchar rsp_check_not_yet_enrolled_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x90}; +static gsize rsp_check_not_yet_enrolled_prefix_len = sizeof (rsp_check_not_yet_enrolled_prefix) / sizeof (rsp_check_not_yet_enrolled_prefix[0]); + + +/* SSM task states and various status enums */ + +typedef enum { + CMD_SEND, + CMD_GET, + CMD_STATES, +} CommandStates; + +typedef enum { + DEV_INIT_CONTROL1, + DEV_INIT_CONTROL2, + DEV_INIT_CONTROL3, + DEV_INIT_CONTROL4, + DEV_INIT_CONTROL5, + DEV_GET_FW_VERSION, + DEV_INIT_STATES, +} DeviceInitStates; + +typedef enum { + IDENTIFY_GET_ENROLLED_IDS, + IDENTIFY_CHECK_ENROLLED_NUM, + IDENTIFY_SENSOR_RESET, + IDENTIFY_SENSOR_IDENTIFY, + IDENTIFY_WAIT_FINGER, + IDENTIFY_SENSOR_CHECK, + IDENTIFY_CHECK, + IDENTIFY_COMPLETE_SENSOR_RESET, + IDENTIFY_COMPLETE, + IDENTIFY_STATES, +} IdentifyStates; + +typedef enum { + ENROLL_GET_ENROLLED_IDS, + ENROLL_CHECK_ENROLLED_NUM, + ENROLL_SENSOR_RESET, + ENROLL_SENSOR_ENROLL, + ENROLL_WAIT_FINGER, + ENROLL_SENSOR_CHECK, + ENROLL_CHECK, + ENROLL_START, + ENROLL_CAPTURE_SENSOR_RESET, + ENROLL_CAPTURE_SENSOR_START_CAPTURE, + ENROLL_CAPTURE_WAIT_FINGER, + ENROLL_CAPTURE_READ_RESPONSE, + ENROLL_COMMIT_START, + ENROLL_COMMIT, + ENROLL_COMMIT_SENSOR_RESET, + ENROLL_COMPLETE, + ENROLL_STATES, +} EnrollStates; + +typedef enum { + ENROLL_STATUS_DEVICE_FULL, + ENROLL_STATUS_DUPLICATE, + ENROLL_STATUS_PARTIAL_OK, + ENROLL_STATUS_RETRY, + ENROLL_STATUS_COMPLETE, +} EnrollStatus; + +typedef enum { + LIST_GET_ENROLLED_IDS, + LIST_RETURN_ENROLLED_PRINTS, + LIST_STATES, +} ListStates; + +typedef enum { + DELETE_GET_ENROLLED_IDS, + DELETE_DELETE, + DELETE_STATES, +} DeleteStates; diff --git a/libfprint/meson.build b/libfprint/meson.build index da8285e5..8eed4dd5 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -119,6 +119,8 @@ driver_sources = { [ 'drivers/etes603.c' ], 'egis0570' : [ 'drivers/egis0570.c' ], + 'egismoc' : + [ 'drivers/egismoc/egismoc.c' ], 'vfs0050' : [ 'drivers/vfs0050.c' ], 'elan' : diff --git a/meson.build b/meson.build index aeef6911..10d17e75 100644 --- a/meson.build +++ b/meson.build @@ -120,6 +120,7 @@ default_drivers = [ 'vfs0050', 'etes603', 'egis0570', + 'egismoc', 'vcom5s', 'synaptics', 'elan', diff --git a/tests/egismoc/custom.pcapng b/tests/egismoc/custom.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..fcd8119ed5643faaa19e43c62c678d40ad71abb0 GIT binary patch literal 73752 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 literal 0 HcmV?d00001 diff --git a/tests/egismoc/custom.py b/tests/egismoc/custom.py new file mode 100755 index 00000000..3a662380 --- /dev/null +++ b/tests/egismoc/custom.py @@ -0,0 +1,156 @@ +#!/usr/bin/python3 + +import traceback +import sys +import time +import gi + +gi.require_version('FPrint', '2.0') +from gi.repository import FPrint, GLib + +# Exit with error on any exception, included those happening in async callbacks +sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1)) + +ctx = GLib.main_context_default() + +c = FPrint.Context() +c.enumerate() +devices = c.get_devices() + +d = devices[0] +del devices + +d.open_sync() + +assert d.get_driver() == "egismoc" +assert not d.has_feature(FPrint.DeviceFeature.CAPTURE) +assert d.has_feature(FPrint.DeviceFeature.IDENTIFY) +assert d.has_feature(FPrint.DeviceFeature.VERIFY) +assert d.has_feature(FPrint.DeviceFeature.DUPLICATES_CHECK) +assert d.has_feature(FPrint.DeviceFeature.STORAGE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_LIST) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) + +def enroll_progress(*args): + print("finger status: ", d.get_finger_status()) + print('enroll progress: ' + str(args)) + +def identify_done(dev, res): + global identified + identified = True + identify_match, identify_print = dev.identify_finish(res) + print('indentification_done: ', identify_match, identify_print) + assert identify_match.equal(identify_print) + +# Beginning with list and clear assumes you begin with >0 prints enrolled before capturing + +print("listing - device should have prints") +stored = d.list_prints_sync() +assert len(stored) > 0 +del stored + +print("clear device storage") +d.clear_storage_sync() +print("clear done") + +print("listing - device should be empty") +stored = d.list_prints_sync() +assert len(stored) == 0 +del stored + +print("enrolling") +template = FPrint.Print.new(d) +template.set_finger(FPrint.Finger.LEFT_INDEX) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p1 = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll done") +del template + +print("listing - device should have 1 print") +stored = d.list_prints_sync() +assert len(stored) == 1 +assert stored[0].equal(p1) + +print("verifying") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +verify_res, verify_print = d.verify_sync(p1) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("verify done") +assert verify_res == True + +identified = False +deserialized_prints = [] +for p in stored: + deserialized_prints.append(FPrint.Print.deserialize(p.serialize())) + assert deserialized_prints[-1].equal(p) +del stored + +print('async identifying') +d.identify(deserialized_prints, callback=identify_done) +del deserialized_prints + +while not identified: + ctx.iteration(True) + +print("try to enroll duplicate") +template = FPrint.Print.new(d) +template.set_finger(FPrint.Finger.RIGHT_INDEX) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +try: + d.enroll_sync(template, None, enroll_progress, None) +except GLib.Error as error: + assert error.matches(FPrint.DeviceError.quark(), + FPrint.DeviceError.DATA_DUPLICATE) +except Exception as exc: + raise +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("duplicate enroll attempt done") + +print("listing - device should still only have 1 print") +stored = d.list_prints_sync() +assert len(stored) == 1 +assert stored[0].equal(p1) +del stored + +print("enroll new finger") +template = FPrint.Print.new(d) +template.set_finger(FPrint.Finger.RIGHT_INDEX) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p2 = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll new finger done") +del template + +print("listing - device should have 2 prints") +stored = d.list_prints_sync() +assert len(stored) == 2 +assert (stored[0].equal(p1) and stored[1].equal(p2)) or (stored[0].equal(p2) and stored[1].equal(p1)) +del stored + +print("deleting first print") +d.delete_print_sync(p1) +print("delete done") +del p1 + +print("listing - device should only have second print") +stored = d.list_prints_sync() +assert len(stored) == 1 +assert stored[0].equal(p2) +del stored +del p2 + +print("clear device storage") +d.clear_storage_sync() +print("clear done") + +print("listing - device should be empty") +stored = d.list_prints_sync() +assert len(stored) == 0 +del stored + +d.close_sync() + +del d +del c diff --git a/tests/egismoc/device b/tests/egismoc/device new file mode 100644 index 00000000..6bd912a0 --- /dev/null +++ b/tests/egismoc/device @@ -0,0 +1,262 @@ +P: /devices/pci0000:00/0000:00:14.0/usb3/3-5 +N: bus/usb/003/012=12010002FF0000407A1C820581110102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005 +E: BUSNUM=003 +E: CURRENT_TAGS=:snap_cups_ippeveprinter:snap_cups_cupsd: +E: DEVNAME=/dev/bus/usb/003/012 +E: DEVNUM=012 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_BUS=usb +E: ID_MODEL=ETU905A80-E +E: ID_MODEL_ENC=ETU905A80-E +E: ID_MODEL_ID=0582 +E: ID_PATH=pci-0000:00:14.0-usb-0:5 +E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_5 +E: ID_REVISION=1181 +E: ID_SERIAL=EGIS_ETU905A80-E_0E7828PBS393 +E: ID_SERIAL_SHORT=0E7828PBS393 +E: ID_USB_INTERFACES=:ff0000: +E: ID_USB_MODEL=ETU905A80-E +E: ID_USB_MODEL_ENC=ETU905A80-E +E: ID_USB_MODEL_ID=0582 +E: ID_USB_REVISION=1181 +E: ID_USB_SERIAL=EGIS_ETU905A80-E_0E7828PBS393 +E: ID_USB_SERIAL_SHORT=0E7828PBS393 +E: ID_USB_VENDOR=EGIS +E: ID_USB_VENDOR_ENC=EGIS +E: ID_USB_VENDOR_ID=1c7a +E: ID_VENDOR=EGIS +E: ID_VENDOR_ENC=EGIS +E: ID_VENDOR_FROM_DATABASE=LighTuning Technology Inc. +E: ID_VENDOR_ID=1c7a +E: MAJOR=189 +E: MINOR=267 +E: PRODUCT=1c7a/582/1181 +E: SUBSYSTEM=usb +E: TAGS=:snap_cups_ippeveprinter:snap_cups_cupsd: +E: TYPE=255/0/0 +A: authorized=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=ff\n +A: bDeviceProtocol=00\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=100mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=1181\n +A: bmAttributes=a0\n +A: busnum=3\n +A: configuration= +H: descriptors=12010002FF0000407A1C820581110102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005 +A: dev=189:267\n +A: devnum=12\n +A: devpath=5\n +L: driver=../../../../../bus/usb/drivers/usb +L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:51/device:52/device:57 +A: idProduct=0582\n +A: idVendor=1c7a\n +A: ltm_capable=no\n +A: manufacturer=EGIS\n +A: maxchild=0\n +A: physical_location/dock=no\n +A: physical_location/horizontal_position=center\n +A: physical_location/lid=no\n +A: physical_location/panel=unknown\n +A: physical_location/vertical_position=center\n +L: port=../3-0:1.0/usb3-port5 +A: power/active_duration=1425996\n +A: power/async=enabled\n +A: power/autosuspend=2\n +A: power/autosuspend_delay_ms=2000\n +A: power/connected_duration=1426656\n +A: power/control=on\n +A: power/level=on\n +A: power/persist=0\n +A: power/runtime_active_kids=0\n +A: power/runtime_active_time=1426124\n +A: power/runtime_enabled=forbidden\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=1\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=ETU905A80-E\n +A: quirks=0x0\n +A: removable=fixed\n +A: rx_lanes=1\n +A: serial=0E7828PBS393\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=2803\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0/usb3 +N: bus/usb/003/001=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C +E: BUSNUM=003 +E: CURRENT_TAGS=:seat:snap_cups_cupsd:snap_cups_ippeveprinter: +E: DEVNAME=/dev/bus/usb/003/001 +E: DEVNUM=001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_AUTOSUSPEND=1 +E: ID_BUS=usb +E: ID_FOR_SEAT=usb-pci-0000_00_14_0 +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_MODEL_ID=0002 +E: ID_PATH=pci-0000:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_REVISION=0602 +E: ID_SERIAL=Linux_6.2.0-34-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_SERIAL_SHORT=0000:00:14.0 +E: ID_USB_INTERFACES=:090000: +E: ID_USB_MODEL=xHCI_Host_Controller +E: ID_USB_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_USB_MODEL_ID=0002 +E: ID_USB_REVISION=0602 +E: ID_USB_SERIAL=Linux_6.2.0-34-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_USB_SERIAL_SHORT=0000:00:14.0 +E: ID_USB_VENDOR=Linux_6.2.0-34-generic_xhci-hcd +E: ID_USB_VENDOR_ENC=Linux\x206.2.0-34-generic\x20xhci-hcd +E: ID_USB_VENDOR_ID=1d6b +E: ID_VENDOR=Linux_6.2.0-34-generic_xhci-hcd +E: ID_VENDOR_ENC=Linux\x206.2.0-34-generic\x20xhci-hcd +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_VENDOR_ID=1d6b +E: MAJOR=189 +E: MINOR=256 +E: PRODUCT=1d6b/2/602 +E: SUBSYSTEM=usb +E: TAGS=:snap_cups_cupsd:seat:snap_cups_ippeveprinter: +E: TYPE=9/0/1 +A: authorized=1\n +A: authorized_default=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=09\n +A: bDeviceProtocol=01\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=0mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0602\n +A: bmAttributes=e0\n +A: busnum=3\n +A: configuration= +H: descriptors=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C +A: dev=189:256\n +A: devnum=1\n +A: devpath=0\n +L: driver=../../../../bus/usb/drivers/usb +L: firmware_node=../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:51/device:52 +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 6.2.0-34-generic xhci-hcd\n +A: maxchild=12\n +A: power/active_duration=337953872\n +A: power/async=enabled\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=337978524\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=337962424\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=616\n +A: power/runtime_usage=0\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=xHCI Host Controller\n +A: quirks=0x0\n +A: removable=unknown\n +A: rx_lanes=1\n +A: serial=0000:00:14.0\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=4969\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0 +E: DRIVER=xhci_hcd +E: ID_MODEL_FROM_DATABASE=Alder Lake PCH USB 3.2 xHCI Host Controller +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_VENDOR_FROM_DATABASE=Intel Corporation +E: MODALIAS=pci:v00008086d000051EDsv0000144Dsd0000C870bc0Csc03i30 +E: PCI_CLASS=C0330 +E: PCI_ID=8086:51ED +E: PCI_SLOT_NAME=0000:00:14.0 +E: PCI_SUBSYS_ID=144D:C870 +E: SUBSYSTEM=pci +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=8680ED51060490020130030C000080000400161D6000000000000000000000000000000000000000000000004D1470C8000000007000000000000000FF010000FD0134A089C27F8000000000000000003F6DD80F000000000000000000000000316000000000000000000000000000000180C2C1080000000000000000000000059087007805E0FE000000000000000009B014F01000400100000000C10A080000080E00001800008F50020000010000090000018680C00009001014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B50F010112000000 +A: consistent_dma_mask_bits=64\n +A: d3cold_allowed=1\n +A: dbc=disabled\n +A: device=0x51ed\n +A: dma_mask_bits=64\n +L: driver=../../../bus/pci/drivers/xhci_hcd +A: driver_override=(null)\n +A: enable=1\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:51 +A: index=7\n +L: iommu=../../virtual/iommu/dmar1 +L: iommu_group=../../../kernel/iommu_groups/8 +A: irq=145\n +A: label=Onboard - Other\n +A: local_cpulist=0-15\n +A: local_cpus=ffff\n +A: modalias=pci:v00008086d000051EDsv0000144Dsd0000C870bc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/145=msi\n +A: numa_node=-1\n +A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 6 9 2112 9\nxHCI ring segments 26 34 4096 34\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 32 128 1\nbuffer-32 0 0 32 0\n +A: power/async=enabled\n +A: power/control=auto\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=337964621\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=438\n +A: power/runtime_usage=0\n +A: power/wakeup=enabled\n +A: power/wakeup_abort_count=0\n +A: power/wakeup_active=0\n +A: power/wakeup_active_count=7\n +A: power/wakeup_count=0\n +A: power/wakeup_expire_count=7\n +A: power/wakeup_last_time_ms=336554844\n +A: power/wakeup_max_time_ms=105\n +A: power/wakeup_total_time_ms=721\n +A: power_state=D0\n +A: resource=0x000000601d160000 0x000000601d16ffff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n +A: revision=0x01\n +A: subsystem_device=0xc870\n +A: subsystem_vendor=0x144d\n +A: vendor=0x8086\n + diff --git a/tests/meson.build b/tests/meson.build index 199500d2..c919ec6e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -51,6 +51,7 @@ drivers_tests = [ 'goodixmoc', 'nb1010', 'egis0570', + 'egismoc', 'fpcmoc', 'realtek', 'focaltech_moc', From a9269980eb3d38995e4b1194e6be1135db3230c3 Mon Sep 17 00:00:00 2001 From: Joshua Grisham <18266314+joshuagrisham@users.noreply.github.com> Date: Mon, 16 Oct 2023 02:57:45 +0200 Subject: [PATCH 39/79] egismoc: Fix crash during close --- libfprint/drivers/egismoc/egismoc.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 0b249c44..aeac0b6e 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -1385,26 +1385,10 @@ egismoc_close (FpDevice *device) GError *error = NULL; egismoc_cancel (device); + g_clear_object (&self->interrupt_cancellable); g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error); fpi_device_close_complete (device, error); - - if (self->task_ssm) - fpi_ssm_free (self->task_ssm); - self->task_ssm = NULL; - - if (self->cmd_ssm) - fpi_ssm_free (self->cmd_ssm); - self->cmd_ssm = NULL; - - self->cmd_transfer = NULL; - - g_clear_object (&self->interrupt_cancellable); - - if (self->enrolled_ids) - g_ptr_array_free (self->enrolled_ids, TRUE); - self->enrolled_ids = NULL; - self->enrolled_num = -1; } static void From 7aae2181e288ecacd004b08b3eae9d5c3daf6462 Mon Sep 17 00:00:00 2001 From: Joshua Grisham <18266314+joshuagrisham@users.noreply.github.com> Date: Fri, 20 Oct 2023 19:54:23 +0200 Subject: [PATCH 40/79] egismoc: Add support for usernames longer than 8 chars --- libfprint/drivers/egismoc/egismoc.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index aeac0b6e..64727067 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -350,16 +350,26 @@ egismoc_exec_cmd (FpDevice *device, static void egismoc_set_print_data (FpPrint *print, - const guchar *device_print_id) + const guchar *device_print_id, + const gchar *user_id) { - g_autofree gchar *user_id = g_malloc (EGISMOC_FINGERPRINT_DATA_SIZE + 1); GVariant *print_id_var = NULL; GVariant *fpi_data = NULL; + g_autofree gchar *fill_user_id = NULL; - memcpy (user_id, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); - memset (user_id + EGISMOC_FINGERPRINT_DATA_SIZE, '\0', sizeof (gchar)); + if (user_id) + { + fill_user_id = g_strdup (user_id); + } + else + { + fill_user_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE + 1); + memcpy (fill_user_id, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); + memset (fill_user_id + EGISMOC_FINGERPRINT_DATA_SIZE, '\0', sizeof (gchar)); + } + + fpi_print_fill_from_user_id (print, fill_user_id); - fpi_print_fill_from_user_id (print, user_id); fpi_print_set_type (print, FPI_PRINT_RAW); fpi_print_set_device_stored (print, TRUE); @@ -384,7 +394,7 @@ egismoc_get_enrolled_prints (FpDevice *device) for (int i = 0; i < self->enrolled_num; i++) { print = fp_print_new (device); - egismoc_set_print_data (print, g_ptr_array_index (self->enrolled_ids, i)); + egismoc_set_print_data (print, g_ptr_array_index (self->enrolled_ids, i), NULL); g_ptr_array_add (result, g_object_ref_sink (print)); } @@ -990,7 +1000,7 @@ egismoc_enroll_run_state (FpiSsm *ssm, device_print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE); memcpy (device_print_id, user_id, MIN (strlen (user_id), EGISMOC_FINGERPRINT_DATA_SIZE)); - egismoc_set_print_data (enroll_print->print, device_print_id); + egismoc_set_print_data (enroll_print->print, device_print_id, user_id); /* create new dynamic payload of cmd_new_print_prefix + device_print_id */ payload_length = cmd_new_print_prefix_len + EGISMOC_FINGERPRINT_DATA_SIZE; @@ -1066,9 +1076,9 @@ egismoc_identify_check_cb (FpDevice *device, buffer_in + EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET, EGISMOC_FINGERPRINT_DATA_SIZE); - /* Create a new print from this ID and then see if it matches the one indicated */ + /* Create a new print from this device_print_id and then see if it matches the one indicated */ print = fp_print_new (device); - egismoc_set_print_data (print, device_print_id); + egismoc_set_print_data (print, device_print_id, NULL); if (!print) { From 47fe3668e4eb079fed0bc6ce3ea8ce26854508dc Mon Sep 17 00:00:00 2001 From: Joshua Grisham <18266314+joshuagrisham@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:18:33 +0200 Subject: [PATCH 41/79] egismoc: Return truncated text instead of null for description when using list --- libfprint/drivers/egismoc/egismoc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 64727067..0aa31e3c 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -373,7 +373,7 @@ egismoc_set_print_data (FpPrint *print, fpi_print_set_type (print, FPI_PRINT_RAW); fpi_print_set_device_stored (print, TRUE); - g_object_set (print, "description", user_id, NULL); + g_object_set (print, "description", fill_user_id, NULL); print_id_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, device_print_id, From 85da0e104bb2cc674c98776d085b0f2aec46a114 Mon Sep 17 00:00:00 2001 From: Joshua Grisham <18266314+joshuagrisham@users.noreply.github.com> Date: Sat, 28 Oct 2023 15:25:16 +0200 Subject: [PATCH 42/79] egismoc: add support for 1c7a:05a1 --- libfprint/drivers/egismoc/egismoc.c | 41 ++++++++++++++++++----------- libfprint/drivers/egismoc/egismoc.h | 22 +++++++++------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 0aa31e3c..dfae716b 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -38,8 +38,9 @@ G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_DEVICE); static const FpIdEntry egismoc_id_table[] = { - { .vid = 0x1c7a, .pid = 0x0582 }, - { .vid = 0, .pid = 0 } + { .vid = 0x1c7a, .pid = 0x0582, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 }, + { .vid = 0x1c7a, .pid = 0x05a1, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE2 }, + { .vid = 0, .pid = 0, .driver_data = 0 } }; typedef void (*SynCmdMsgCallback) (FpDevice *device, @@ -808,10 +809,10 @@ egismoc_enroll_check_cb (FpDevice *device, } /* Check that the read payload reports "not yet enrolled" */ - if (egismoc_validate_response_prefix (buffer_in, + if (egismoc_validate_response_suffix (buffer_in, length_in, - rsp_check_not_yet_enrolled_prefix, - rsp_check_not_yet_enrolled_prefix_len)) + rsp_check_not_yet_enrolled_suffix, + rsp_check_not_yet_enrolled_suffix_len)) fpi_ssm_next_state (self->task_ssm); else egismoc_enroll_status_report (device, NULL, ENROLL_STATUS_DUPLICATE, @@ -846,9 +847,13 @@ egismoc_get_check_cmd (FpDevice *device, const gsize body_length = sizeof (guchar) * self->enrolled_num * EGISMOC_FINGERPRINT_DATA_SIZE; + /* prefix length can depend on the type */ + const gsize check_prefix_length = (fpi_device_get_driver_data (device) & EGISMOC_DRIVER_CHECK_PREFIX_TYPE2) ? + cmd_check_prefix_type2_len : cmd_check_prefix_type1_len; + /* total_length is the 6 various bytes plus all other prefixes/suffixes and the body payload */ const gsize total_length = (sizeof (guchar) * 6) - + cmd_check_prefix_len + + check_prefix_length + EGISMOC_CMD_CHECK_SEPARATOR_LENGTH + body_length + cmd_check_suffix_len; @@ -878,8 +883,16 @@ egismoc_get_check_cmd (FpDevice *device, } /* command prefix */ - memcpy (result + pos, cmd_check_prefix, cmd_check_prefix_len); - pos += cmd_check_prefix_len; + if (fpi_device_get_driver_data (device) & EGISMOC_DRIVER_CHECK_PREFIX_TYPE2) + { + memcpy (result + pos, cmd_check_prefix_type2, cmd_check_prefix_type2_len); + pos += cmd_check_prefix_type2_len; + } + else + { + memcpy (result + pos, cmd_check_prefix_type1, cmd_check_prefix_type1_len); + pos += cmd_check_prefix_type1_len; + } /* 2-bytes size logic for counter again */ if (self->enrolled_num > 6) @@ -1059,11 +1072,7 @@ egismoc_identify_check_cb (FpDevice *device, } /* Check that the read payload indicates "match" */ - if (egismoc_validate_response_prefix (buffer_in, - length_in, - rsp_identify_match_prefix, - rsp_identify_match_prefix_len) && - egismoc_validate_response_suffix (buffer_in, + if (egismoc_validate_response_suffix (buffer_in, length_in, rsp_identify_match_suffix, rsp_identify_match_suffix_len)) @@ -1119,10 +1128,10 @@ egismoc_identify_check_cb (FpDevice *device, } } /* If device was successfully read but it was a "not matched" */ - else if (egismoc_validate_response_prefix (buffer_in, + else if (egismoc_validate_response_suffix (buffer_in, length_in, - rsp_identify_notmatch_prefix, - rsp_identify_notmatch_prefix_len)) + rsp_identify_notmatch_suffix, + rsp_identify_notmatch_suffix_len)) { fp_info ("Print was not identified by the device"); diff --git a/libfprint/drivers/egismoc/egismoc.h b/libfprint/drivers/egismoc/egismoc.h index c30bac1b..65b50322 100644 --- a/libfprint/drivers/egismoc/egismoc.h +++ b/libfprint/drivers/egismoc/egismoc.h @@ -33,6 +33,10 @@ G_DECLARE_FINAL_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FPI, DEVICE_EGISMOC, FpDevice) #define EGISMOC_DRIVER_FULLNAME "Egis Technology (LighTuning) Match-on-Chip" + +#define EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 (1 << 0) +#define EGISMOC_DRIVER_CHECK_PREFIX_TYPE2 (1 << 1) + #define EGISMOC_EP_CMD_OUT (0x02 | FPI_USB_ENDPOINT_OUT) #define EGISMOC_EP_CMD_IN (0x81 | FPI_USB_ENDPOINT_IN) #define EGISMOC_EP_CMD_INTERRUPT_IN 0x83 @@ -40,7 +44,7 @@ G_DECLARE_FINAL_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FPI, DEVICE_EGISMOC, #define EGISMOC_USB_CONTROL_TIMEOUT 5000 #define EGISMOC_USB_SEND_TIMEOUT 5000 #define EGISMOC_USB_RECV_TIMEOUT 5000 -#define EGISMOC_USB_INTERRUPT_TIMEOUT 0 +#define EGISMOC_USB_INTERRUPT_TIMEOUT 60000 #define EGISMOC_USB_IN_RECV_LENGTH 4096 #define EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH 64 @@ -91,12 +95,10 @@ static gsize cmd_sensor_check_len = sizeof (cmd_sensor_check) / sizeof (cmd_sens static guchar cmd_sensor_identify[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x17, 0x01, 0x01}; static gsize cmd_sensor_identify_len = sizeof (cmd_sensor_identify) / sizeof (cmd_sensor_identify[0]); -static guchar rsp_identify_match_prefix[] = {0x00, 0x00, 0x00, 0x42}; -static gsize rsp_identify_match_prefix_len = sizeof (rsp_identify_match_prefix) / sizeof (rsp_identify_match_prefix[0]); static guchar rsp_identify_match_suffix[] = {0x90, 0x00}; static gsize rsp_identify_match_suffix_len = sizeof (rsp_identify_match_suffix) / sizeof (rsp_identify_match_suffix[0]); -static guchar rsp_identify_notmatch_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x90, 0x04}; -static gsize rsp_identify_notmatch_prefix_len = sizeof (rsp_identify_notmatch_prefix) / sizeof (rsp_identify_notmatch_prefix[0]); +static guchar rsp_identify_notmatch_suffix[] = {0x90, 0x04}; +static gsize rsp_identify_notmatch_suffix_len = sizeof (rsp_identify_notmatch_suffix) / sizeof (rsp_identify_notmatch_suffix[0]); static guchar cmd_sensor_enroll[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x17, 0x01, 0x00}; static gsize cmd_sensor_enroll_len = sizeof (cmd_sensor_enroll) / sizeof (cmd_sensor_enroll[0]); @@ -151,12 +153,14 @@ static gsize cmd_delete_prefix_len = sizeof (cmd_delete_prefix) / sizeof (cmd_de static guchar rsp_delete_success_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x90, 0x00}; static gsize rsp_delete_success_prefix_len = sizeof (rsp_delete_success_prefix) / sizeof (rsp_delete_success_prefix[0]); -static guchar cmd_check_prefix[] = {0x50, 0x17, 0x03, 0x00, 0x00}; -static gsize cmd_check_prefix_len = sizeof (cmd_check_prefix) / sizeof (cmd_check_prefix[0]); +static guchar cmd_check_prefix_type1[] = {0x50, 0x17, 0x03, 0x00, 0x00}; +static gsize cmd_check_prefix_type1_len = sizeof (cmd_check_prefix_type1) / sizeof (cmd_check_prefix_type1[0]); +static guchar cmd_check_prefix_type2[] = {0x50, 0x17, 0x03, 0x80, 0x00}; +static gsize cmd_check_prefix_type2_len = sizeof (cmd_check_prefix_type2) / sizeof (cmd_check_prefix_type2[0]); static guchar cmd_check_suffix[] = {0x00, 0x40}; static gsize cmd_check_suffix_len = sizeof (cmd_check_suffix) / sizeof (cmd_check_suffix[0]); -static guchar rsp_check_not_yet_enrolled_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x90}; -static gsize rsp_check_not_yet_enrolled_prefix_len = sizeof (rsp_check_not_yet_enrolled_prefix) / sizeof (rsp_check_not_yet_enrolled_prefix[0]); +static guchar rsp_check_not_yet_enrolled_suffix[] = {0x90, 0x04}; +static gsize rsp_check_not_yet_enrolled_suffix_len = sizeof (rsp_check_not_yet_enrolled_suffix) / sizeof (rsp_check_not_yet_enrolled_suffix[0]); /* SSM task states and various status enums */ From 89ab54794e9857c428b0d718e4efba2d505ce3f0 Mon Sep 17 00:00:00 2001 From: Joshua Grisham <18266314+joshuagrisham@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:39:32 +0100 Subject: [PATCH 43/79] egismoc: added test and capture for 05a1 variant --- tests/egismoc-05a1/custom.pcapng | Bin 0 -> 62756 bytes tests/egismoc-05a1/custom.py | 156 ++++++++++++++++++ tests/egismoc-05a1/device | 262 +++++++++++++++++++++++++++++++ tests/meson.build | 1 + 4 files changed, 419 insertions(+) create mode 100644 tests/egismoc-05a1/custom.pcapng create mode 100755 tests/egismoc-05a1/custom.py create mode 100644 tests/egismoc-05a1/device diff --git a/tests/egismoc-05a1/custom.pcapng b/tests/egismoc-05a1/custom.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..4821c01222b66340a8066289ebc3f7aaf69b2298 GIT binary patch literal 62756 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 F{vW14HAw&f literal 0 HcmV?d00001 diff --git a/tests/egismoc-05a1/custom.py b/tests/egismoc-05a1/custom.py new file mode 100755 index 00000000..3a662380 --- /dev/null +++ b/tests/egismoc-05a1/custom.py @@ -0,0 +1,156 @@ +#!/usr/bin/python3 + +import traceback +import sys +import time +import gi + +gi.require_version('FPrint', '2.0') +from gi.repository import FPrint, GLib + +# Exit with error on any exception, included those happening in async callbacks +sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1)) + +ctx = GLib.main_context_default() + +c = FPrint.Context() +c.enumerate() +devices = c.get_devices() + +d = devices[0] +del devices + +d.open_sync() + +assert d.get_driver() == "egismoc" +assert not d.has_feature(FPrint.DeviceFeature.CAPTURE) +assert d.has_feature(FPrint.DeviceFeature.IDENTIFY) +assert d.has_feature(FPrint.DeviceFeature.VERIFY) +assert d.has_feature(FPrint.DeviceFeature.DUPLICATES_CHECK) +assert d.has_feature(FPrint.DeviceFeature.STORAGE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_LIST) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) + +def enroll_progress(*args): + print("finger status: ", d.get_finger_status()) + print('enroll progress: ' + str(args)) + +def identify_done(dev, res): + global identified + identified = True + identify_match, identify_print = dev.identify_finish(res) + print('indentification_done: ', identify_match, identify_print) + assert identify_match.equal(identify_print) + +# Beginning with list and clear assumes you begin with >0 prints enrolled before capturing + +print("listing - device should have prints") +stored = d.list_prints_sync() +assert len(stored) > 0 +del stored + +print("clear device storage") +d.clear_storage_sync() +print("clear done") + +print("listing - device should be empty") +stored = d.list_prints_sync() +assert len(stored) == 0 +del stored + +print("enrolling") +template = FPrint.Print.new(d) +template.set_finger(FPrint.Finger.LEFT_INDEX) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p1 = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll done") +del template + +print("listing - device should have 1 print") +stored = d.list_prints_sync() +assert len(stored) == 1 +assert stored[0].equal(p1) + +print("verifying") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +verify_res, verify_print = d.verify_sync(p1) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("verify done") +assert verify_res == True + +identified = False +deserialized_prints = [] +for p in stored: + deserialized_prints.append(FPrint.Print.deserialize(p.serialize())) + assert deserialized_prints[-1].equal(p) +del stored + +print('async identifying') +d.identify(deserialized_prints, callback=identify_done) +del deserialized_prints + +while not identified: + ctx.iteration(True) + +print("try to enroll duplicate") +template = FPrint.Print.new(d) +template.set_finger(FPrint.Finger.RIGHT_INDEX) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +try: + d.enroll_sync(template, None, enroll_progress, None) +except GLib.Error as error: + assert error.matches(FPrint.DeviceError.quark(), + FPrint.DeviceError.DATA_DUPLICATE) +except Exception as exc: + raise +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("duplicate enroll attempt done") + +print("listing - device should still only have 1 print") +stored = d.list_prints_sync() +assert len(stored) == 1 +assert stored[0].equal(p1) +del stored + +print("enroll new finger") +template = FPrint.Print.new(d) +template.set_finger(FPrint.Finger.RIGHT_INDEX) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p2 = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll new finger done") +del template + +print("listing - device should have 2 prints") +stored = d.list_prints_sync() +assert len(stored) == 2 +assert (stored[0].equal(p1) and stored[1].equal(p2)) or (stored[0].equal(p2) and stored[1].equal(p1)) +del stored + +print("deleting first print") +d.delete_print_sync(p1) +print("delete done") +del p1 + +print("listing - device should only have second print") +stored = d.list_prints_sync() +assert len(stored) == 1 +assert stored[0].equal(p2) +del stored +del p2 + +print("clear device storage") +d.clear_storage_sync() +print("clear done") + +print("listing - device should be empty") +stored = d.list_prints_sync() +assert len(stored) == 0 +del stored + +d.close_sync() + +del d +del c diff --git a/tests/egismoc-05a1/device b/tests/egismoc-05a1/device new file mode 100644 index 00000000..49f1e9ea --- /dev/null +++ b/tests/egismoc-05a1/device @@ -0,0 +1,262 @@ +P: /devices/pci0000:00/0000:00:14.0/usb1/1-5 +N: bus/usb/001/003=12010002FF0000407A1CA10513120102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005 +E: BUSNUM=001 +E: DEVNAME=/dev/bus/usb/001/003 +E: DEVNUM=003 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_BUS=usb +E: ID_MODEL=ETU905A80-E +E: ID_MODEL_ENC=ETU905A80-E +E: ID_MODEL_ID=05a1 +E: ID_REVISION=1213 +E: ID_SERIAL=EGIS_ETU905A80-E_0C5A44PCU833 +E: ID_SERIAL_SHORT=0C5A44PCU833 +E: ID_USB_INTERFACES=:ff0000: +E: ID_USB_MODEL=ETU905A80-E +E: ID_USB_MODEL_ENC=ETU905A80-E +E: ID_USB_MODEL_ID=05a1 +E: ID_USB_REVISION=1213 +E: ID_USB_SERIAL=EGIS_ETU905A80-E_0C5A44PCU833 +E: ID_USB_SERIAL_SHORT=0C5A44PCU833 +E: ID_USB_VENDOR=EGIS +E: ID_USB_VENDOR_ENC=EGIS +E: ID_USB_VENDOR_ID=1c7a +E: ID_VENDOR=EGIS +E: ID_VENDOR_ENC=EGIS +E: ID_VENDOR_FROM_DATABASE=LighTuning Technology Inc. +E: ID_VENDOR_ID=1c7a +E: MAJOR=189 +E: MINOR=2 +E: PRODUCT=1c7a/5a1/1213 +E: SUBSYSTEM=usb +E: TYPE=255/0/0 +A: authorized=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=ff\n +A: bDeviceProtocol=00\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=100mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=1213\n +A: bmAttributes=a0\n +A: busnum=1\n +A: configuration= +H: descriptors=12010002FF0000407A1CA10513120102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005 +A: dev=189:2\n +A: devnum=3\n +A: devpath=5\n +L: driver=../../../../../bus/usb/drivers/usb +L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:51/device:52/device:57 +A: idProduct=05a1\n +A: idVendor=1c7a\n +A: ltm_capable=no\n +A: manufacturer=EGIS\n +A: maxchild=0\n +A: physical_location/dock=no\n +A: physical_location/horizontal_position=center\n +A: physical_location/lid=no\n +A: physical_location/panel=unknown\n +A: physical_location/vertical_position=center\n +L: port=../1-0:1.0/usb1-port5 +A: power/active_duration=955612\n +A: power/async=enabled\n +A: power/autosuspend=2\n +A: power/autosuspend_delay_ms=2000\n +A: power/connected_duration=955612\n +A: power/control=on\n +A: power/level=on\n +A: power/persist=0\n +A: power/runtime_active_kids=0\n +A: power/runtime_active_time=955338\n +A: power/runtime_enabled=forbidden\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=1\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=ETU905A80-E\n +A: quirks=0x0\n +A: removable=fixed\n +A: rx_lanes=1\n +A: serial=0C5A44PCU833\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=491\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0/usb1 +N: bus/usb/001/001=12010002090001406B1D020005060302010109021900010100E0000904000001090000000705810304000C +E: BUSNUM=001 +E: CURRENT_TAGS=:seat: +E: DEVNAME=/dev/bus/usb/001/001 +E: DEVNUM=001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_AUTOSUSPEND=1 +E: ID_BUS=usb +E: ID_FOR_SEAT=usb-pci-0000_00_14_0 +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_MODEL_ID=0002 +E: ID_PATH=pci-0000:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_REVISION=0605 +E: ID_SERIAL=Linux_6.5.0-9-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_SERIAL_SHORT=0000:00:14.0 +E: ID_USB_INTERFACES=:090000: +E: ID_USB_MODEL=xHCI_Host_Controller +E: ID_USB_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_USB_MODEL_ID=0002 +E: ID_USB_REVISION=0605 +E: ID_USB_SERIAL=Linux_6.5.0-9-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_USB_SERIAL_SHORT=0000:00:14.0 +E: ID_USB_VENDOR=Linux_6.5.0-9-generic_xhci-hcd +E: ID_USB_VENDOR_ENC=Linux\x206.5.0-9-generic\x20xhci-hcd +E: ID_USB_VENDOR_ID=1d6b +E: ID_VENDOR=Linux_6.5.0-9-generic_xhci-hcd +E: ID_VENDOR_ENC=Linux\x206.5.0-9-generic\x20xhci-hcd +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_VENDOR_ID=1d6b +E: MAJOR=189 +E: MINOR=0 +E: PRODUCT=1d6b/2/605 +E: SUBSYSTEM=usb +E: TAGS=:seat: +E: TYPE=9/0/1 +A: authorized=1\n +A: authorized_default=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=09\n +A: bDeviceProtocol=01\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=0mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0605\n +A: bmAttributes=e0\n +A: busnum=1\n +A: configuration= +H: descriptors=12010002090001406B1D020005060302010109021900010100E0000904000001090000000705810304000C +A: dev=189:0\n +A: devnum=1\n +A: devpath=0\n +L: driver=../../../../bus/usb/drivers/usb +L: firmware_node=../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:51/device:52 +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 6.5.0-9-generic xhci-hcd\n +A: maxchild=12\n +A: power/active_duration=956044\n +A: power/async=enabled\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=956044\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_kids=2\n +A: power/runtime_active_time=956041\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=0\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=xHCI Host Controller\n +A: quirks=0x0\n +A: removable=unknown\n +A: rx_lanes=1\n +A: serial=0000:00:14.0\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=181\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0 +E: DRIVER=xhci_hcd +E: ID_MODEL_FROM_DATABASE=Alder Lake PCH USB 3.2 xHCI Host Controller +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_VENDOR_FROM_DATABASE=Intel Corporation +E: MODALIAS=pci:v00008086d000051EDsv0000144Dsd0000C1CAbc0Csc03i30 +E: PCI_CLASS=C0330 +E: PCI_ID=8086:51ED +E: PCI_SLOT_NAME=0000:00:14.0 +E: PCI_SUBSYS_ID=144D:C1CA +E: SUBSYSTEM=pci +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=8680ED51060490020130030C0000800004001A3E6000000000000000000000000000000000000000000000004D14CAC1000000007000000000000000FF010000FD0134A089C27F8000000000000000003F6DD80F000000000000000000000000316000000000000000000000000000000180C2C108000000000000000000000005908700D804E0FE000000000000000009B014F01000400100000000C10A080000080E00001800008F50020000010000090000018680C00009001014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B50F110112000000 +A: consistent_dma_mask_bits=64\n +A: d3cold_allowed=1\n +A: dbc=disabled\n +A: dbc_bInterfaceProtocol=01\n +A: dbc_bcdDevice=0010\n +A: dbc_idProduct=0010\n +A: dbc_idVendor=1d6b\n +A: device=0x51ed\n +A: dma_mask_bits=64\n +L: driver=../../../bus/pci/drivers/xhci_hcd +A: driver_override=(null)\n +A: enable=1\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:51 +A: index=9\n +L: iommu=../../virtual/iommu/dmar2 +L: iommu_group=../../../kernel/iommu_groups/11 +A: irq=142\n +A: label=Onboard - Other\n +A: local_cpulist=0-15\n +A: local_cpus=ffff\n +A: modalias=pci:v00008086d000051EDsv0000144Dsd0000C1CAbc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/142=msi\n +A: numa_node=-1\n +A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 6 7 2112 7\nxHCI ring segments 28 28 4096 28\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 1 32 128 1\nbuffer-32 0 0 32 0\n +A: power/async=enabled\n +A: power/control=auto\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=957198\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=0\n +A: power/wakeup=enabled\n +A: power/wakeup_abort_count=0\n +A: power/wakeup_active=0\n +A: power/wakeup_active_count=0\n +A: power/wakeup_count=0\n +A: power/wakeup_expire_count=0\n +A: power/wakeup_last_time_ms=0\n +A: power/wakeup_max_time_ms=0\n +A: power/wakeup_total_time_ms=0\n +A: power_state=D0\n +A: resource=0x000000603e1a0000 0x000000603e1affff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n +A: revision=0x01\n +A: subsystem_device=0xc1ca\n +A: subsystem_vendor=0x144d\n +A: vendor=0x8086\n + diff --git a/tests/meson.build b/tests/meson.build index c919ec6e..efa573fb 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -52,6 +52,7 @@ drivers_tests = [ 'nb1010', 'egis0570', 'egismoc', + 'egismoc-05a1', 'fpcmoc', 'realtek', 'focaltech_moc', From 7476faba689d9f7e70ebd82ccd75dd319798e4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Feb 2024 15:08:50 +0100 Subject: [PATCH 44/79] data, libfprint: Update list of unsupported drivers --- data/autosuspend.hwdb | 8 ++++++-- libfprint/fprint-list-udev-hwdb.c | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 215a96b3..74ac65b0 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -77,6 +77,12 @@ usb:v1C7Ap0571* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver egismoc +usb:v1C7Ap0582* +usb:v1C7Ap05A1* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver elan usb:v04F3p0903* usb:v04F3p0907* @@ -372,8 +378,6 @@ usb:v1C7Ap0300* usb:v1C7Ap0575* usb:v1C7Ap0576* usb:v1C7Ap0577* -usb:v1C7Ap0582* -usb:v1C7Ap05A1* usb:v27C6p5042* usb:v27C6p5110* usb:v27C6p5117* diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index 28530a06..bace9f93 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -101,8 +101,6 @@ static const FpIdEntry allowlist_id_table[] = { { .vid = 0x1c7a, .pid = 0x0575 }, { .vid = 0x1c7a, .pid = 0x0576 }, { .vid = 0x1c7a, .pid = 0x0577 }, - { .vid = 0x1c7a, .pid = 0x0582 }, - { .vid = 0x1c7a, .pid = 0x05a1 }, { .vid = 0x27c6, .pid = 0x5042 }, { .vid = 0x27c6, .pid = 0x5110 }, { .vid = 0x27c6, .pid = 0x5117 }, From b0f0322726fecf6dece6216ca1b2b2ea60bfee7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:31:11 +0100 Subject: [PATCH 45/79] egismoc: Indentation and syntax fixes --- libfprint/drivers/egismoc/egismoc.c | 316 ++++++++++++++++++---------- libfprint/drivers/egismoc/egismoc.h | 13 -- 2 files changed, 202 insertions(+), 127 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index dfae716b..f4ed08b0 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -35,6 +35,18 @@ #include "egismoc.h" +struct _FpiDeviceEgisMoc +{ + FpDevice parent; + FpiSsm *task_ssm; + FpiSsm *cmd_ssm; + FpiUsbTransfer *cmd_transfer; + GCancellable *interrupt_cancellable; + + int enrolled_num; + GPtrArray *enrolled_ids; +}; + G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_DEVICE); static const FpIdEntry egismoc_id_table[] = { @@ -84,9 +96,11 @@ egismoc_wait_finger_on_sensor (FpiSsm *ssm, g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device); - fpi_usb_transfer_fill_interrupt (transfer, EGISMOC_EP_CMD_INTERRUPT_IN, EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH); + fpi_usb_transfer_fill_interrupt (transfer, EGISMOC_EP_CMD_INTERRUPT_IN, + EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH); transfer->ssm = ssm; - transfer->short_is_error = FALSE; /* Interrupt on this device always returns 1 byte short; this is expected */ + /* Interrupt on this device always returns 1 byte short; this is expected */ + transfer->short_is_error = FALSE; fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED); @@ -103,7 +117,9 @@ egismoc_validate_response_prefix (const guchar *buffer_in, const guchar *valid_prefix, const gsize valid_prefix_len) { - const gboolean result = memcmp (buffer_in + (egismoc_read_prefix_len + EGISMOC_CHECK_BYTES_LENGTH), + const gboolean result = memcmp (buffer_in + + (egismoc_read_prefix_len + + EGISMOC_CHECK_BYTES_LENGTH), valid_prefix, valid_prefix_len) == 0; @@ -166,9 +182,10 @@ egismoc_cmd_receive_cb (FpiUsbTransfer *transfer, gpointer userdata, GError *error) { - fp_dbg ("Command receive callback"); CommandData *data = userdata; + fp_dbg ("Command receive callback"); + if (error) { fpi_ssm_mark_failed (transfer->ssm, error); @@ -206,17 +223,17 @@ egismoc_cmd_run_state (FpiSsm *ssm, NULL, fpi_ssm_usb_transfer_cb, NULL); + break; } - else - { - fpi_ssm_next_state (ssm); - } + + fpi_ssm_next_state (ssm); break; case CMD_GET: transfer = fpi_usb_transfer_new (device); transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk (transfer, EGISMOC_EP_CMD_IN, EGISMOC_USB_IN_RECV_LENGTH); + fpi_usb_transfer_fill_bulk (transfer, EGISMOC_EP_CMD_IN, + EGISMOC_USB_IN_RECV_LENGTH); fpi_usb_transfer_submit (g_steal_pointer (&transfer), EGISMOC_USB_RECV_TIMEOUT, NULL, @@ -253,9 +270,9 @@ typedef union egismoc_check_bytes } EgisMocCheckBytes; /* - Derive the 2 "check bytes" for write payloads - 32-bit big-endian sum of all 16-bit words (including check bytes) MOD 0xFFFF should be 0, otherwise - the device will reject the payload + * Derive the 2 "check bytes" for write payloads + * 32-bit big-endian sum of all 16-bit words (including check bytes) MOD 0xFFFF + * should be 0, otherwise the device will reject the payload */ static EgisMocCheckBytes egismoc_get_check_bytes (const guchar *value, @@ -279,7 +296,7 @@ egismoc_get_check_bytes (const guchar *value, sum_shorts += value_bigendian_shorts[i]; /* - derive the "first possible occurence" of check bytes as: + derive the "first possible occurrence" of check bytes as: `0xFFFF - (sum_of_32bit_words % 0xFFFF) */ check_bytes.check_short = 0xffff - (sum_shorts % 0xffff); @@ -310,10 +327,12 @@ egismoc_exec_cmd (FpDevice *device, transfer->short_is_error = TRUE; /* - buffer_out should be a fully composed command (with prefix, check bytes, etc) which looks like this - E G I S 00 00 00 01 {cb1} {cb2} {payload} - where cb1 and cb2 are some check bytes generated by the egismoc_get_check_bytes method - and payload is what is passed via the cmd parameter + * buffer_out should be a fully composed command (with prefix, check bytes, etc) + * which looks like this: + * E G I S 00 00 00 01 {cb1} {cb2} {payload} + * where cb1 and cb2 are some check bytes generated by the + * egismoc_get_check_bytes() method and payload is what is passed via the cmd + * parameter */ buffer_out_length = egismoc_write_prefix_len + EGISMOC_CHECK_BYTES_LENGTH @@ -323,18 +342,22 @@ egismoc_exec_cmd (FpDevice *device, /* Prefix */ memcpy (buffer_out, egismoc_write_prefix, egismoc_write_prefix_len); - /* Check Bytes - leave them as 00 for now then later generate and copy over the real ones */ + /* Check Bytes - leave them as 00 for now then later generate and copy over + * the real ones */ /* Command Payload */ - memcpy (buffer_out + egismoc_write_prefix_len + EGISMOC_CHECK_BYTES_LENGTH, cmd, cmd_length); + memcpy (buffer_out + egismoc_write_prefix_len + EGISMOC_CHECK_BYTES_LENGTH, + cmd, cmd_length); /* destroy cmd if requested */ if (cmd_destroy) cmd_destroy (cmd); - /* Now fetch and set the "real" check bytes based on the currently assembled payload */ + /* Now fetch and set the "real" check bytes based on the currently + * assembled payload */ check_bytes = egismoc_get_check_bytes (buffer_out, buffer_out_length); - memcpy (buffer_out + egismoc_write_prefix_len, check_bytes.check_bytes, EGISMOC_CHECK_BYTES_LENGTH); + memcpy (buffer_out + egismoc_write_prefix_len, check_bytes.check_bytes, + EGISMOC_CHECK_BYTES_LENGTH); fpi_usb_transfer_fill_bulk_full (transfer, EGISMOC_EP_CMD_OUT, @@ -423,9 +446,9 @@ egismoc_list_fill_enrolled_ids_cb (FpDevice *device, self->enrolled_num = 0; /* - Each fingerprint ID will be returned in this response as a 32 byte array - The other stuff in the payload is 16 bytes long, so if there is at least 1 print - then the length should be at least 16+32=48 bytes long + * Each fingerprint ID will be returned in this response as a 32 byte array + * The other stuff in the payload is 16 bytes long, so if there is at least 1 + * print then the length should be at least 16+32=48 bytes long */ for (int pos = EGISMOC_LIST_RESPONSE_PREFIX_SIZE; pos < length_in - EGISMOC_LIST_RESPONSE_SUFFIX_SIZE; @@ -433,11 +456,13 @@ egismoc_list_fill_enrolled_ids_cb (FpDevice *device, { g_autofree guchar *print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE); memcpy (print_id, buffer_in + pos, EGISMOC_FINGERPRINT_DATA_SIZE); - fp_dbg ("Device fingerprint %0d: %.*s%c", self->enrolled_num, EGISMOC_FINGERPRINT_DATA_SIZE, print_id, '\0'); + fp_dbg ("Device fingerprint %0d: %.*s", self->enrolled_num, + EGISMOC_FINGERPRINT_DATA_SIZE, print_id); g_ptr_array_add (self->enrolled_ids, g_steal_pointer (&print_id)); } - fp_info ("Number of currently enrolled fingerprints on the device is %d", self->enrolled_num); + fp_info ("Number of currently enrolled fingerprints on the device is %d", + self->enrolled_num); if (self->task_ssm) fpi_ssm_next_state (self->task_ssm); @@ -452,7 +477,8 @@ egismoc_list_run_state (FpiSsm *ssm, switch (fpi_ssm_get_cur_state (ssm)) { case LIST_GET_ENROLLED_IDS: - egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, + egismoc_list_fill_enrolled_ids_cb); break; case LIST_RETURN_ENROLLED_PRINTS: @@ -482,30 +508,35 @@ egismoc_get_delete_cmd (FpDevice *device, { fp_dbg ("Get delete command"); FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); - g_autofree const gchar *print_description = NULL; - g_autoptr(GVariant) print_data = NULL; g_autoptr(GVariant) print_data_id_var = NULL; const guchar *print_data_id = NULL; gsize print_data_id_len = 0; + g_autofree gchar *print_description = NULL; g_autofree guchar *enrolled_print_id = NULL; - guchar *result = NULL; + g_autofree guchar *result = NULL; gsize pos = 0; /* - The final command body should contain: - 1) hard-coded 00 00 - 2) 2-byte size indiciator, 20*Number deleted identifiers plus 7 in form of: num_to_delete * 0x20 + 0x07 - Since max prints can be higher than 7 then this goes up to 2 bytes (e9 + 9 = 109) - 3) Hard-coded prefix (cmd_delete_prefix) - 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 7 (num_to_delete * 0x20) - 5) All of the currently registered prints to delete in their 32-byte device identifiers (enrolled_list) + * The final command body should contain: + * 1) hard-coded 00 00 + * 2) 2-byte size indiciator, 20*Number deleted identifiers plus 7 in form of: + * num_to_delete * 0x20 + 0x07 + * Since max prints can be higher than 7 then this goes up to 2 bytes + * (e9 + 9 = 109) + * 3) Hard-coded prefix (cmd_delete_prefix) + * 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 7 + * (num_to_delete * 0x20) + * 5) All of the currently registered prints to delete in their 32-byte device + * identifiers (enrolled_list) */ const int num_to_delete = (!delete_print) ? self->enrolled_num : 1; - const gsize body_length = sizeof (guchar) * EGISMOC_FINGERPRINT_DATA_SIZE * num_to_delete; + const gsize body_length = sizeof (guchar) * EGISMOC_FINGERPRINT_DATA_SIZE * + num_to_delete; /* total_length is the 6 various bytes plus prefix and body payload */ - const gsize total_length = (sizeof (guchar) * 6) + cmd_delete_prefix_len + body_length; + const gsize total_length = (sizeof (guchar) * 6) + cmd_delete_prefix_len + + body_length; /* pre-fill entire payload with 00s */ result = g_malloc0 (total_length); @@ -514,8 +545,10 @@ egismoc_get_delete_cmd (FpDevice *device, pos = 2; /* Size Counter bytes */ - /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for when we go to the 2nd byte */ - /* note this will not work in case any model ever supports more than 14 prints (assumed max is 10) */ + /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for + * when we go to the 2nd byte + * note this will not work in case any model ever supports more than 14 prints + * (assumed max is 10) */ if (num_to_delete > 7) { memset (result + pos, 0x01, sizeof (guchar)); @@ -560,15 +593,18 @@ egismoc_get_delete_cmd (FpDevice *device, if (!g_variant_check_format_string (print_data, "(@ay)", FALSE)) { - fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); return NULL; } g_variant_get (print_data, "(@ay)", &print_data_id_var); - print_data_id = g_variant_get_fixed_array (print_data_id_var, &print_data_id_len, sizeof (guchar)); + print_data_id = g_variant_get_fixed_array (print_data_id_var, + &print_data_id_len, sizeof (guchar)); if (!g_str_has_prefix (print_description, "FP")) - fp_dbg ("Fingerprint '%s' was not created by libfprint; deleting anyway.", print_description); + fp_dbg ("Fingerprint '%s' was not created by libfprint; deleting anyway.", + print_description); fp_info ("Delete fingerprint %s (%s)", print_description, print_data_id); @@ -623,14 +659,16 @@ egismoc_delete_cb (FpDevice *device, } else { - fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, - "Unsupported delete action.")); + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Unsupported delete action.")); } } else { - fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, - "Delete print was not successfull")); + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Delete print was not successful")); } } @@ -644,17 +682,22 @@ egismoc_delete_run_state (FpiSsm *ssm, switch (fpi_ssm_get_cur_state (ssm)) { case DELETE_GET_ENROLLED_IDS: - /* get enrolled_ids and enrolled_num from device for use building delete payload below */ - egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + /* get enrolled_ids and enrolled_num from device for use building + * delete payload below + */ + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, + egismoc_list_fill_enrolled_ids_cb); break; case DELETE_DELETE: if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE) - payload = egismoc_get_delete_cmd (device, fpi_ssm_get_data (ssm), &payload_length); + payload = egismoc_get_delete_cmd (device, fpi_ssm_get_data (ssm), + &payload_length); else payload = egismoc_get_delete_cmd (device, NULL, &payload_length); - egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_delete_cb); + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, + g_free, egismoc_delete_cb); break; } } @@ -725,7 +768,8 @@ egismoc_enroll_status_report (FpDevice *device, fpi_ssm_mark_failed (self->task_ssm, error); else fpi_ssm_mark_failed (self->task_ssm, - fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Unknown error")); + fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "Unknown error")); } } @@ -755,7 +799,8 @@ egismoc_read_capture_cb (FpDevice *device, rsp_read_success_suffix, rsp_read_success_suffix_len)) { - egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_PARTIAL_OK, NULL); + egismoc_enroll_status_report (device, enroll_print, + ENROLL_STATUS_PARTIAL_OK, NULL); } else { @@ -778,11 +823,13 @@ egismoc_read_capture_cb (FpDevice *device, rsp_read_dirty_prefix, rsp_read_dirty_prefix_len)) error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, - "Your device is having trouble recognizing you. Make sure your sensor is clean."); + "Your device is having trouble recognizing you. " + "Make sure your sensor is clean."); else error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, - "Unknown failure trying to read your finger. Please try again."); + "Unknown failure trying to read your finger. " + "Please try again."); egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_RETRY, error); } @@ -820,8 +867,9 @@ egismoc_enroll_check_cb (FpDevice *device, } /* - Builds the full "check" payload which includes identifiers for all fingerprints which currently - should exist on the storage. This payload is used during both enrollment and verify actions. + * Builds the full "check" payload which includes identifiers for all + * fingerprints which currently should exist on the storage. This payload is + * used during both enrollment and verify actions. */ static guchar * egismoc_get_check_cmd (FpDevice *device, @@ -829,29 +877,36 @@ egismoc_get_check_cmd (FpDevice *device, { fp_dbg ("Get check command"); FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); - guchar *device_print_id = NULL; - guchar *result = NULL; + g_autofree guchar *result = NULL; gsize pos = 0; /* - The final command body should contain: - 1) hard-coded 00 00 - 2) 2-byte size indiciator, 20*Number enrolled identifiers plus 9 in form of: (enrolled_num + 1) * 0x20 + 0x09 - Since max prints can be higher than 7 then this goes up to 2 bytes (e9 + 9 = 109) - 3) Hard-coded prefix (cmd_check_prefix) - 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 9 ((enrolled_num + 1) * 0x20) - 5) Hard-coded 32 * 0x00 bytes - 6) All of the currently registered prints in their 32-byte device identifiers (enrolled_list) - 7) Hard-coded suffix (cmd_check_suffix) + * The final command body should contain: + * 1) hard-coded 00 00 + * 2) 2-byte size indiciator, 20*Number enrolled identifiers plus 9 in form of: + * (enrolled_num + 1) * 0x20 + 0x09 + * Since max prints can be higher than 7 then this goes up to 2 bytes + * (e9 + 9 = 109) + * 3) Hard-coded prefix (cmd_check_prefix) + * 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 9 + * ((enrolled_num + 1) * 0x20) + * 5) Hard-coded 32 * 0x00 bytes + * 6) All of the currently registered prints in their 32-byte device identifiers + * (enrolled_list) + * 7) Hard-coded suffix (cmd_check_suffix) */ - const gsize body_length = sizeof (guchar) * self->enrolled_num * EGISMOC_FINGERPRINT_DATA_SIZE; + const gsize body_length = sizeof (guchar) * self->enrolled_num * + EGISMOC_FINGERPRINT_DATA_SIZE; /* prefix length can depend on the type */ - const gsize check_prefix_length = (fpi_device_get_driver_data (device) & EGISMOC_DRIVER_CHECK_PREFIX_TYPE2) ? - cmd_check_prefix_type2_len : cmd_check_prefix_type1_len; + const gsize check_prefix_length = (fpi_device_get_driver_data (device) & + EGISMOC_DRIVER_CHECK_PREFIX_TYPE2) ? + cmd_check_prefix_type2_len : + cmd_check_prefix_type1_len; - /* total_length is the 6 various bytes plus all other prefixes/suffixes and the body payload */ + /* total_length is the 6 various bytes plus all other prefixes/suffixes and + * the body payload */ const gsize total_length = (sizeof (guchar) * 6) + check_prefix_length + EGISMOC_CMD_CHECK_SEPARATOR_LENGTH @@ -865,20 +920,24 @@ egismoc_get_check_cmd (FpDevice *device, pos = 2; /* Size Counter bytes */ - /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for when we go to the 2nd byte */ - /* note this will not work in case any model ever supports more than 14 prints (assumed max is 10) */ + /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for + * when we go to the 2nd byte + * note this will not work in case any model ever supports more than 14 prints + * (assumed max is 10) */ if (self->enrolled_num > 6) { memset (result + pos, 0x01, sizeof (guchar)); pos += sizeof (guchar); - memset (result + pos, ((self->enrolled_num - 7) * 0x20) + 0x09, sizeof (guchar)); + memset (result + pos, ((self->enrolled_num - 7) * 0x20) + 0x09, + sizeof (guchar)); pos += sizeof (guchar); } else { /* first byte is 0x00, just skip it */ pos += sizeof (guchar); - memset (result + pos, ((self->enrolled_num + 1) * 0x20) + 0x09, sizeof (guchar)); + memset (result + pos, ((self->enrolled_num + 1) * 0x20) + 0x09, + sizeof (guchar)); pos += sizeof (guchar); } @@ -918,7 +977,7 @@ egismoc_get_check_cmd (FpDevice *device, for (int i = 0; i < self->enrolled_num; i++) { - device_print_id = g_ptr_array_index (self->enrolled_ids, i); + gchar *device_print_id = g_ptr_array_index (self->enrolled_ids, i); memcpy (result + pos + (print_id_length * i), device_print_id, print_id_length); } pos += body_length; @@ -948,7 +1007,8 @@ egismoc_enroll_run_state (FpiSsm *ssm, { case ENROLL_GET_ENROLLED_IDS: /* get enrolled_ids and enrolled_num from device for use in check stages below */ - egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + egismoc_exec_cmd (device, cmd_list, cmd_list_len, + NULL, egismoc_list_fill_enrolled_ids_cb); break; case ENROLL_CHECK_ENROLLED_NUM: @@ -962,11 +1022,13 @@ egismoc_enroll_run_state (FpiSsm *ssm, break; case ENROLL_SENSOR_RESET: - egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, + NULL, egismoc_task_ssm_next_state_cb); break; case ENROLL_SENSOR_ENROLL: - egismoc_exec_cmd (device, cmd_sensor_enroll, cmd_sensor_enroll_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_sensor_enroll, cmd_sensor_enroll_len, + NULL, egismoc_task_ssm_next_state_cb); break; case ENROLL_WAIT_FINGER: @@ -974,24 +1036,29 @@ egismoc_enroll_run_state (FpiSsm *ssm, break; case ENROLL_SENSOR_CHECK: - egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, + NULL, egismoc_task_ssm_next_state_cb); break; case ENROLL_CHECK: payload = egismoc_get_check_cmd (device, &payload_length); - egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_enroll_check_cb); + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, + g_free, egismoc_enroll_check_cb); break; case ENROLL_START: - egismoc_exec_cmd (device, cmd_enroll_starting, cmd_enroll_starting_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_enroll_starting, cmd_enroll_starting_len, + NULL, egismoc_task_ssm_next_state_cb); break; case ENROLL_CAPTURE_SENSOR_RESET: - egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, + NULL, egismoc_task_ssm_next_state_cb); break; case ENROLL_CAPTURE_SENSOR_START_CAPTURE: - egismoc_exec_cmd (device, cmd_sensor_start_capture, cmd_sensor_start_capture_len, NULL, + egismoc_exec_cmd (device, cmd_sensor_start_capture, cmd_sensor_start_capture_len, + NULL, egismoc_task_ssm_next_state_cb); break; @@ -1000,11 +1067,13 @@ egismoc_enroll_run_state (FpiSsm *ssm, break; case ENROLL_CAPTURE_READ_RESPONSE: - egismoc_exec_cmd (device, cmd_read_capture, cmd_read_capture_len, NULL, egismoc_read_capture_cb); + egismoc_exec_cmd (device, cmd_read_capture, cmd_read_capture_len, + NULL, egismoc_read_capture_cb); break; case ENROLL_COMMIT_START: - egismoc_exec_cmd (device, cmd_commit_starting, cmd_commit_starting_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_commit_starting, cmd_commit_starting_len, + NULL, egismoc_task_ssm_next_state_cb); break; case ENROLL_COMMIT: @@ -1019,13 +1088,16 @@ egismoc_enroll_run_state (FpiSsm *ssm, payload_length = cmd_new_print_prefix_len + EGISMOC_FINGERPRINT_DATA_SIZE; payload = g_malloc0 (payload_length); memcpy (payload, cmd_new_print_prefix, cmd_new_print_prefix_len); - memcpy (payload + cmd_new_print_prefix_len, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); + memcpy (payload + cmd_new_print_prefix_len, device_print_id, + EGISMOC_FINGERPRINT_DATA_SIZE); - egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, + g_free, egismoc_task_ssm_next_state_cb); break; case ENROLL_COMMIT_SENSOR_RESET: - egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, + NULL, egismoc_task_ssm_next_state_cb); break; case ENROLL_COMPLETE: @@ -1085,7 +1157,9 @@ egismoc_identify_check_cb (FpDevice *device, buffer_in + EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET, EGISMOC_FINGERPRINT_DATA_SIZE); - /* Create a new print from this device_print_id and then see if it matches the one indicated */ + /* Create a new print from this device_print_id and then see if it matches + * the one indicated + */ print = fp_print_new (device); egismoc_set_print_data (print, device_print_id, NULL); @@ -1093,7 +1167,8 @@ egismoc_identify_check_cb (FpDevice *device, { fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, - "Failed to build a print from device response.")); + "Failed to build a print from " + "device response.")); return; } @@ -1148,8 +1223,9 @@ egismoc_identify_check_cb (FpDevice *device, } else { - fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, - "Unrecognized response from device.")); + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Unrecognized response from device.")); } } @@ -1165,7 +1241,8 @@ egismoc_identify_run_state (FpiSsm *ssm, { case IDENTIFY_GET_ENROLLED_IDS: /* get enrolled_ids and enrolled_num from device for use in check stages below */ - egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + egismoc_exec_cmd (device, cmd_list, cmd_list_len, + NULL, egismoc_list_fill_enrolled_ids_cb); break; case IDENTIFY_CHECK_ENROLLED_NUM: @@ -1179,11 +1256,13 @@ egismoc_identify_run_state (FpiSsm *ssm, break; case IDENTIFY_SENSOR_RESET: - egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, + NULL, egismoc_task_ssm_next_state_cb); break; case IDENTIFY_SENSOR_IDENTIFY: - egismoc_exec_cmd (device, cmd_sensor_identify, cmd_sensor_identify_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_sensor_identify, cmd_sensor_identify_len, + NULL, egismoc_task_ssm_next_state_cb); break; case IDENTIFY_WAIT_FINGER: @@ -1191,26 +1270,30 @@ egismoc_identify_run_state (FpiSsm *ssm, break; case IDENTIFY_SENSOR_CHECK: - egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, + NULL, egismoc_task_ssm_next_state_cb); break; case IDENTIFY_CHECK: payload = egismoc_get_check_cmd (device, &payload_length); - egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_identify_check_cb); + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, + g_free, egismoc_identify_check_cb); break; case IDENTIFY_COMPLETE_SENSOR_RESET: - egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, + NULL, egismoc_task_ssm_next_state_cb); break; /* - In Windows, the driver seems at this point to then immediately take another read from the sensor; - this is suspected to be an on-chip "verify". However, because the user's finger is still on the - sensor from the identify, then it seems to always return positive. We will consider this extra - step unnecessary and just skip it in this driver. This driver will instead handle matching of the - FpPrint from the gallery in the "verify" case of the callback egismoc_identify_check_cb. + * In Windows, the driver seems at this point to then immediately take + * another read from the sensor; this is suspected to be an on-chip + * "verify". However, because the user's finger is still on the sensor from + * the identify, then it seems to always return positive. We will consider + * this extra step unnecessary and just skip it in this driver. This driver + * will instead handle matching of the FpPrint from the gallery in the + * "verify" case of the callback egismoc_identify_check_cb. */ - case IDENTIFY_COMPLETE: if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY) fpi_device_identify_complete (device, NULL); @@ -1258,14 +1341,16 @@ egismoc_fw_version_cb (FpDevice *device, { fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, - "Device firmware response was not valid.")); + "Device firmware response " + "was not valid.")); return; } /* - FW Version is 12 bytes: a carriage return (0x0d) plus the version string itself. - Always skip [the read prefix] + [2 * check bytes] + [3 * 0x00] that come with every payload - Then we will also skip the carriage return and take all but the last 2 bytes as the FW Version + * FW Version is 12 bytes: a carriage return (0x0d) plus the version string + * itself. Always skip [the read prefix] + [2 * check bytes] + [3 * 0x00] that + * come with every payload Then we will also skip the carriage return and take + * all but the last 2 bytes as the FW Version */ prefix_length = egismoc_read_prefix_len + 2 + 3 + 1; fw_version_length = length_in - prefix_length - rsp_fw_version_suffix_len; @@ -1346,7 +1431,8 @@ egismoc_dev_init_handler (FpiSsm *ssm, break; case DEV_GET_FW_VERSION: - egismoc_exec_cmd (device, cmd_fw_version, cmd_fw_version_len, NULL, egismoc_fw_version_cb); + egismoc_exec_cmd (device, cmd_fw_version, cmd_fw_version_len, + NULL, egismoc_fw_version_cb); break; } @@ -1406,7 +1492,8 @@ egismoc_close (FpDevice *device) egismoc_cancel (device); g_clear_object (&self->interrupt_cancellable); - g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error); + g_usb_device_release_interface (fpi_device_get_usb_device (device), + 0, 0, &error); fpi_device_close_complete (device, error); } @@ -1428,7 +1515,8 @@ fpi_device_egismoc_class_init (FpiDeviceEgisMocClass *klass) dev_class->scan_type = FP_SCAN_TYPE_PRESS; dev_class->id_table = egismoc_id_table; dev_class->nr_enroll_stages = EGISMOC_ENROLL_TIMES; - dev_class->temp_hot_seconds = 0; /* device should be "always off" unless being used */ + /* device should be "always off" unless being used */ + dev_class->temp_hot_seconds = 0; dev_class->open = egismoc_open; dev_class->cancel = egismoc_cancel; diff --git a/libfprint/drivers/egismoc/egismoc.h b/libfprint/drivers/egismoc/egismoc.h index 65b50322..f027ea23 100644 --- a/libfprint/drivers/egismoc/egismoc.h +++ b/libfprint/drivers/egismoc/egismoc.h @@ -55,19 +55,6 @@ G_DECLARE_FINAL_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FPI, DEVICE_EGISMOC, #define EGISMOC_LIST_RESPONSE_PREFIX_SIZE 14 #define EGISMOC_LIST_RESPONSE_SUFFIX_SIZE 2 -struct _FpiDeviceEgisMoc -{ - FpDevice parent; - FpiSsm *task_ssm; - FpiSsm *cmd_ssm; - FpiUsbTransfer *cmd_transfer; - GCancellable *interrupt_cancellable; - - int enrolled_num; - GPtrArray *enrolled_ids; -}; - - /* standard prefixes for all read/writes */ static guchar egismoc_write_prefix[] = {'E', 'G', 'I', 'S', 0x00, 0x00, 0x00, 0x01}; From a7843add0f7ddceec24cfb6bf8d70ef2be454879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:31:27 +0100 Subject: [PATCH 46/79] egismoc: Do not initialize to zero twice --- libfprint/drivers/egismoc/egismoc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index f4ed08b0..2fa7940f 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -389,7 +389,6 @@ egismoc_set_print_data (FpPrint *print, { fill_user_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE + 1); memcpy (fill_user_id, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); - memset (fill_user_id + EGISMOC_FINGERPRINT_DATA_SIZE, '\0', sizeof (gchar)); } fpi_print_fill_from_user_id (print, fill_user_id); From b8cfb95b49c8a393d14cafc0160e3c47304117c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:33:11 +0100 Subject: [PATCH 47/79] egismoc: Ensure we've enough null bytes at the end of strings --- libfprint/drivers/egismoc/egismoc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 2fa7940f..3a2d41d9 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -453,7 +453,7 @@ egismoc_list_fill_enrolled_ids_cb (FpDevice *device, pos < length_in - EGISMOC_LIST_RESPONSE_SUFFIX_SIZE; pos += EGISMOC_FINGERPRINT_DATA_SIZE, self->enrolled_num++) { - g_autofree guchar *print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE); + g_autofree guchar *print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE + 1); memcpy (print_id, buffer_in + pos, EGISMOC_FINGERPRINT_DATA_SIZE); fp_dbg ("Device fingerprint %0d: %.*s", self->enrolled_num, EGISMOC_FINGERPRINT_DATA_SIZE, print_id); From eb0915624496df62123fc820bd6d2059c55d76da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:34:16 +0100 Subject: [PATCH 48/79] egismoc: Clear enrolled IDs using nicer GLib api --- libfprint/drivers/egismoc/egismoc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 3a2d41d9..7168ca8a 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -152,8 +152,7 @@ egismoc_task_ssm_done (FpiSsm *ssm, /* task_ssm already freed by completion of SSM */ self->task_ssm = NULL; - if (self->enrolled_ids) - g_ptr_array_free (self->enrolled_ids, TRUE); + g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref); self->enrolled_ids = NULL; self->enrolled_num = -1; @@ -439,8 +438,7 @@ egismoc_list_fill_enrolled_ids_cb (FpDevice *device, return; } - if (self->enrolled_ids) - g_ptr_array_free (self->enrolled_ids, TRUE); + g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref); self->enrolled_ids = g_ptr_array_new_with_free_func (g_free); self->enrolled_num = 0; From 8073a5dc34cc95459011e9a4d6f74b26c8f87880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:34:46 +0100 Subject: [PATCH 49/79] egismoc: Remove unused increments This is also to please static analyzer --- libfprint/drivers/egismoc/egismoc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 7168ca8a..e14b31bc 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -615,7 +615,6 @@ egismoc_get_delete_cmd (FpDevice *device, g_ptr_array_index (self->enrolled_ids, i), EGISMOC_FINGERPRINT_DATA_SIZE); } - pos += body_length; if (length_out) *length_out = total_length; @@ -981,7 +980,6 @@ egismoc_get_check_cmd (FpDevice *device, /* command suffix */ memcpy (result + pos, cmd_check_suffix, cmd_check_suffix_len); - pos += cmd_check_suffix_len; if (length_out) *length_out = total_length; From 87f68e3ac11c70fa86a719be8104f5697eb90ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:35:58 +0100 Subject: [PATCH 50/79] egismoc: Avoid gotos in init code, just handle the errors immediately --- libfprint/drivers/egismoc/egismoc.c | 31 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index e14b31bc..d957830c 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -1386,7 +1386,6 @@ egismoc_dev_init_handler (FpiSsm *ssm, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 32, 0x0000, 4, 16); - goto send_control; break; case DEV_INIT_CONTROL2: @@ -1395,7 +1394,6 @@ egismoc_dev_init_handler (FpiSsm *ssm, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 32, 0x0000, 4, 40); - goto send_control; break; case DEV_INIT_CONTROL3: @@ -1404,7 +1402,6 @@ egismoc_dev_init_handler (FpiSsm *ssm, G_USB_DEVICE_REQUEST_TYPE_STANDARD, G_USB_DEVICE_RECIPIENT_DEVICE, 0, 0x0000, 0, 2); - goto send_control; break; case DEV_INIT_CONTROL4: @@ -1413,7 +1410,6 @@ egismoc_dev_init_handler (FpiSsm *ssm, G_USB_DEVICE_REQUEST_TYPE_STANDARD, G_USB_DEVICE_RECIPIENT_DEVICE, 0, 0x0000, 0, 2); - goto send_control; break; case DEV_INIT_CONTROL5: @@ -1422,18 +1418,17 @@ egismoc_dev_init_handler (FpiSsm *ssm, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 82, 0x0000, 0, 8); - goto send_control; break; case DEV_GET_FW_VERSION: egismoc_exec_cmd (device, cmd_fw_version, cmd_fw_version_len, NULL, egismoc_fw_version_cb); - break; + return; + + default: + g_assert_not_reached (); } - return; - -send_control: transfer->ssm = ssm; transfer->short_is_error = TRUE; fpi_usb_transfer_submit (g_steal_pointer (&transfer), @@ -1453,17 +1448,21 @@ egismoc_open (FpDevice *device) self->interrupt_cancellable = g_cancellable_new (); if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) - goto error; + { + fpi_device_open_complete (device, error); + return; + } - if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error)) - goto error; + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), + 0, 0, &error)) + { + fpi_device_open_complete (device, error); + return; + } + g_assert (self->task_ssm == NULL); self->task_ssm = fpi_ssm_new (device, egismoc_dev_init_handler, DEV_INIT_STATES); fpi_ssm_start (self->task_ssm, egismoc_dev_init_done); - return; - -error: - return fpi_device_open_complete (device, error); } static void From 92aeb53ee854412861ddbf0e6d0718d1cc14d6d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:37:47 +0100 Subject: [PATCH 51/79] egismoc: Simplify egismoc identification stage handling --- libfprint/drivers/egismoc/egismoc.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index d957830c..763d9216 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -1181,8 +1181,6 @@ egismoc_identify_check_cb (FpDevice *device, fpi_device_identify_report (device, g_ptr_array_index (prints, index), print, NULL); else fpi_device_identify_report (device, NULL, print, NULL); - - fpi_ssm_next_state (self->task_ssm); } else { @@ -1193,8 +1191,6 @@ egismoc_identify_check_cb (FpDevice *device, fpi_device_verify_report (device, FPI_MATCH_SUCCESS, print, NULL); else fpi_device_verify_report (device, FPI_MATCH_FAIL, print, NULL); - - fpi_ssm_next_state (self->task_ssm); } } /* If device was successfully read but it was a "not matched" */ @@ -1206,22 +1202,19 @@ egismoc_identify_check_cb (FpDevice *device, fp_info ("Print was not identified by the device"); if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) - { - fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL); - fpi_ssm_next_state (self->task_ssm); - } + fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL); else - { - fpi_device_identify_report (device, NULL, NULL, NULL); - fpi_ssm_next_state (self->task_ssm); - } + fpi_device_identify_report (device, NULL, NULL, NULL); } else { fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, "Unrecognized response from device.")); + return; } + + fpi_ssm_next_state (self->task_ssm); } static void From 9e2c14d64e6ef68055c26afb4797142b2b47e805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:38:19 +0100 Subject: [PATCH 52/79] egismoc: Clarify delete print ownership in delete callback --- libfprint/drivers/egismoc/egismoc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 763d9216..2deca613 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -715,15 +715,15 @@ egismoc_delete (FpDevice *device) { fp_dbg ("Delete"); FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); - - g_autoptr(FpPrint) delete_print = NULL; + FpPrint *delete_print = NULL; fpi_device_get_delete_data (device, &delete_print); self->task_ssm = fpi_ssm_new (device, egismoc_delete_run_state, DELETE_STATES); - fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&delete_print), NULL); /* todo leak or cleared by libfprint ? */ + /* the print is owned by libfprint during deletion task */ + fpi_ssm_set_data (self->task_ssm, delete_print, NULL); fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); } From 226b6abfab103283afec87ba54f38b61ff5aba2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:39:42 +0100 Subject: [PATCH 53/79] egismoc: Use an autopointer to cleanup error on command done callback --- libfprint/drivers/egismoc/egismoc.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 2deca613..05caf8d6 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -247,19 +247,15 @@ egismoc_cmd_ssm_done (FpiSsm *ssm, FpDevice *device, GError *error) { + g_autoptr(GError) local_error = error; FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); CommandData *data = fpi_ssm_get_data (ssm); self->cmd_ssm = NULL; self->cmd_transfer = NULL; - if (error) - { - if (data->callback) - data->callback (device, NULL, 0, error); - else - g_error_free (error); - } + if (error && data && data->callback) + data->callback (device, NULL, 0, g_steal_pointer (&local_error)); } typedef union egismoc_check_bytes From 6767cd1a4f9e55d5b16a469eb4d8afc4441b95c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:41:15 +0100 Subject: [PATCH 54/79] egismoc: Ensure that the command callback is after SSM is completed We need to make sure that we won't trigger a callback when a SSM is already in progress or we may end up overwriting it --- libfprint/drivers/egismoc/egismoc.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 05caf8d6..0731aa5b 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -181,7 +181,10 @@ egismoc_cmd_receive_cb (FpiUsbTransfer *transfer, gpointer userdata, GError *error) { + g_autofree guchar *buffer = NULL; CommandData *data = userdata; + SynCmdMsgCallback callback; + gssize actual_length; fp_dbg ("Command receive callback"); @@ -197,10 +200,18 @@ egismoc_cmd_receive_cb (FpiUsbTransfer *transfer, return; } - if (data->callback) - data->callback (device, transfer->buffer, transfer->actual_length, NULL); + /* Let's complete the previous ssm and then handle the callback, so that + * we are sure that we won't start a transfer or a new command while there is + * another one still ongoing + */ + callback = data->callback; + buffer = g_steal_pointer (&transfer->buffer); + actual_length = transfer->actual_length; fpi_ssm_mark_completed (transfer->ssm); + + if (callback) + callback (device, buffer, actual_length, NULL); } static void From 591f9ad3cf38a425f269e237a4a0621b6af01aed Mon Sep 17 00:00:00 2001 From: Joshua Grisham <18266314+joshuagrisham@users.noreply.github.com> Date: Sat, 17 Feb 2024 14:17:58 +0100 Subject: [PATCH 55/79] egismoc: clear task pointers also after dev_init_done --- libfprint/drivers/egismoc/egismoc.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 0731aa5b..18a9ea57 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -1367,9 +1367,15 @@ egismoc_dev_init_done (FpiSsm *ssm, GError *error) { if (error) - g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, NULL); + { + g_usb_device_release_interface ( + fpi_device_get_usb_device (device), 0, 0, NULL); + egismoc_task_ssm_done (ssm, device, error); + return; + } - fpi_device_open_complete (device, error); + egismoc_task_ssm_done (ssm, device, NULL); + fpi_device_open_complete (device, NULL); } static void From b97efa6fedd284b053e534d0e810503844dfdb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:44:46 +0100 Subject: [PATCH 56/79] egismoc: Assert that task ssm is unset when setting it We need to ensure that we are not overwriting the instance SSM, so that we can be sure that we are only doing one operation at time. Also we need to ensure that the task unsetting it, is the owner of it. --- libfprint/drivers/egismoc/egismoc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 18a9ea57..08e514bd 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -149,7 +149,8 @@ egismoc_task_ssm_done (FpiSsm *ssm, fp_dbg ("Task SSM done"); FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); - /* task_ssm already freed by completion of SSM */ + /* task_ssm is going to be freed by completion of SSM */ + g_assert (!self->task_ssm || self->task_ssm == ssm); self->task_ssm = NULL; g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref); @@ -325,6 +326,7 @@ egismoc_exec_cmd (FpDevice *device, g_autoptr(FpiUsbTransfer) transfer = NULL; CommandData *data = g_new0 (CommandData, 1); + g_assert (self->cmd_ssm == NULL); self->cmd_ssm = fpi_ssm_new (device, egismoc_cmd_run_state, CMD_STATES); @@ -499,6 +501,7 @@ egismoc_list (FpDevice *device) fp_dbg ("List"); FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + g_assert (self->task_ssm == NULL); self->task_ssm = fpi_ssm_new (device, egismoc_list_run_state, LIST_STATES); @@ -711,6 +714,7 @@ egismoc_clear_storage (FpDevice *device) fp_dbg ("Clear storage"); FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + g_assert (self->task_ssm == NULL); self->task_ssm = fpi_ssm_new (device, egismoc_delete_run_state, DELETE_STATES); @@ -726,6 +730,7 @@ egismoc_delete (FpDevice *device) fpi_device_get_delete_data (device, &delete_print); + g_assert (self->task_ssm == NULL); self->task_ssm = fpi_ssm_new (device, egismoc_delete_run_state, DELETE_STATES); @@ -1119,6 +1124,7 @@ egismoc_enroll (FpDevice *device) fpi_device_get_enroll_data (device, &enroll_print->print); enroll_print->stage = 0; + g_assert (self->task_ssm == NULL); self->task_ssm = fpi_ssm_new (device, egismoc_enroll_run_state, ENROLL_STATES); fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&enroll_print), g_free); fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); @@ -1306,6 +1312,7 @@ egismoc_identify_verify (FpDevice *device) fp_dbg ("Identify or Verify"); FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + g_assert (self->task_ssm == NULL); self->task_ssm = fpi_ssm_new (device, egismoc_identify_run_state, IDENTIFY_STATES); fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); } From 8badfa84e9ab13c946dea416d69599f6cf285106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:45:29 +0100 Subject: [PATCH 57/79] egismoc: Assert that current transfer is unset when setting it We need to ensure that we are not overwriting the instance transfer, so that we can be sure that we are only doing one transfer at time. Also we need to ensure that the ssm unsetting it, is the owner of it. --- libfprint/drivers/egismoc/egismoc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 08e514bd..0673bfed 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -263,6 +263,9 @@ egismoc_cmd_ssm_done (FpiSsm *ssm, FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); CommandData *data = fpi_ssm_get_data (ssm); + g_assert (self->cmd_ssm == ssm); + g_assert (!self->cmd_transfer || self->cmd_transfer->ssm == ssm); + self->cmd_ssm = NULL; self->cmd_transfer = NULL; @@ -373,6 +376,8 @@ egismoc_exec_cmd (FpDevice *device, buffer_out_length, g_free); transfer->ssm = self->cmd_ssm; + + g_assert (self->cmd_transfer == NULL); self->cmd_transfer = g_steal_pointer (&transfer); data->callback = callback; From 904bddd98808305bfd1ed319a7f06fb45e023463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 15:52:43 +0100 Subject: [PATCH 58/79] egismoc: Use g_new0 instead of g_malloc to make the type clearer --- libfprint/drivers/egismoc/egismoc.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 0673bfed..f7a30c3e 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -348,7 +348,7 @@ egismoc_exec_cmd (FpDevice *device, buffer_out_length = egismoc_write_prefix_len + EGISMOC_CHECK_BYTES_LENGTH + cmd_length; - buffer_out = g_malloc0 (buffer_out_length); + buffer_out = g_new0 (guchar, buffer_out_length); /* Prefix */ memcpy (buffer_out, egismoc_write_prefix, egismoc_write_prefix_len); @@ -400,7 +400,7 @@ egismoc_set_print_data (FpPrint *print, } else { - fill_user_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE + 1); + fill_user_id = g_new0 (gchar, EGISMOC_FINGERPRINT_DATA_SIZE + 1); memcpy (fill_user_id, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); } @@ -465,7 +465,7 @@ egismoc_list_fill_enrolled_ids_cb (FpDevice *device, pos < length_in - EGISMOC_LIST_RESPONSE_SUFFIX_SIZE; pos += EGISMOC_FINGERPRINT_DATA_SIZE, self->enrolled_num++) { - g_autofree guchar *print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE + 1); + g_autofree guchar *print_id = g_new0 (guchar, EGISMOC_FINGERPRINT_DATA_SIZE + 1); memcpy (print_id, buffer_in + pos, EGISMOC_FINGERPRINT_DATA_SIZE); fp_dbg ("Device fingerprint %0d: %.*s", self->enrolled_num, EGISMOC_FINGERPRINT_DATA_SIZE, print_id); @@ -551,7 +551,7 @@ egismoc_get_delete_cmd (FpDevice *device, body_length; /* pre-fill entire payload with 00s */ - result = g_malloc0 (total_length); + result = g_new0 (guchar, total_length); /* start with 00 00 (just move starting offset up by 2) */ pos = 2; @@ -927,7 +927,7 @@ egismoc_get_check_cmd (FpDevice *device, + cmd_check_suffix_len; /* pre-fill entire payload with 00s */ - result = g_malloc0 (total_length); + result = g_new0 (guchar, total_length); /* start with 00 00 (just move starting offset up by 2) */ pos = 2; @@ -1092,13 +1092,13 @@ egismoc_enroll_run_state (FpiSsm *ssm, user_id = fpi_print_generate_user_id (enroll_print->print); fp_dbg ("New fingerprint ID: %s", user_id); - device_print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE); + device_print_id = g_new0 (guchar, EGISMOC_FINGERPRINT_DATA_SIZE); memcpy (device_print_id, user_id, MIN (strlen (user_id), EGISMOC_FINGERPRINT_DATA_SIZE)); egismoc_set_print_data (enroll_print->print, device_print_id, user_id); /* create new dynamic payload of cmd_new_print_prefix + device_print_id */ payload_length = cmd_new_print_prefix_len + EGISMOC_FINGERPRINT_DATA_SIZE; - payload = g_malloc0 (payload_length); + payload = g_new0 (guchar, payload_length); memcpy (payload, cmd_new_print_prefix, cmd_new_print_prefix_len); memcpy (payload + cmd_new_print_prefix_len, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); @@ -1361,7 +1361,7 @@ egismoc_fw_version_cb (FpDevice *device, */ prefix_length = egismoc_read_prefix_len + 2 + 3 + 1; fw_version_length = length_in - prefix_length - rsp_fw_version_suffix_len; - fw_version = g_malloc0 (fw_version_length + 1); + fw_version = g_new0 (guchar, fw_version_length + 1); memcpy (fw_version, buffer_in + prefix_length, From 9af211cc89841353ad7d522f816686e6ce0b7c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 16:13:07 +0100 Subject: [PATCH 59/79] egismoc: Use device cancellable on transfers --- libfprint/drivers/egismoc/egismoc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index f7a30c3e..3bc46b6f 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -231,7 +231,7 @@ egismoc_cmd_run_state (FpiSsm *ssm, self->cmd_transfer->ssm = ssm; fpi_usb_transfer_submit (g_steal_pointer (&self->cmd_transfer), EGISMOC_USB_SEND_TIMEOUT, - NULL, + fpi_device_get_cancellable (device), fpi_ssm_usb_transfer_cb, NULL); break; @@ -247,7 +247,7 @@ egismoc_cmd_run_state (FpiSsm *ssm, EGISMOC_USB_IN_RECV_LENGTH); fpi_usb_transfer_submit (g_steal_pointer (&transfer), EGISMOC_USB_RECV_TIMEOUT, - NULL, + fpi_device_get_cancellable (device), egismoc_cmd_receive_cb, fpi_ssm_get_data (ssm)); break; @@ -1451,7 +1451,7 @@ egismoc_dev_init_handler (FpiSsm *ssm, transfer->short_is_error = TRUE; fpi_usb_transfer_submit (g_steal_pointer (&transfer), EGISMOC_USB_CONTROL_TIMEOUT, - NULL, + fpi_device_get_cancellable (device), fpi_ssm_usb_transfer_cb, NULL); } From adc66edd8dfb95a3ff2fbc02704580fa0504283d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 16:34:33 +0100 Subject: [PATCH 60/79] egismoc: Implement suspension properly In case of suspension we can't just cancel the operations but also return when completed, and this may not happen immediately if there are ongoing operations. This is automagically handled by libfprint internals, but in order to make it happen, we need to cancel the ongoing operations and then mark it completed. libfprint will then wait for the task completion before actually marking the device as suspended. --- libfprint/drivers/egismoc/egismoc.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 3bc46b6f..811a817c 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -1494,6 +1494,16 @@ egismoc_cancel (FpDevice *device) self->interrupt_cancellable = g_cancellable_new (); } +static void +egismoc_suspend (FpDevice *device) +{ + fp_dbg ("Suspend"); + + egismoc_cancel (device); + g_cancellable_cancel (fpi_device_get_cancellable (device)); + fpi_device_suspend_complete (device, NULL); +} + static void egismoc_close (FpDevice *device) { @@ -1532,7 +1542,7 @@ fpi_device_egismoc_class_init (FpiDeviceEgisMocClass *klass) dev_class->open = egismoc_open; dev_class->cancel = egismoc_cancel; - dev_class->suspend = egismoc_cancel; + dev_class->suspend = egismoc_suspend; dev_class->close = egismoc_close; dev_class->identify = egismoc_identify_verify; dev_class->verify = egismoc_identify_verify; From f8f28a066b9cc4a682f6ae61cc768c13daea3ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 16:56:35 +0100 Subject: [PATCH 61/79] egismoc: Simplify fingerprint id and firmware reading We can do copy and duplicate in oneshot since we are handling strings after all. --- libfprint/drivers/egismoc/egismoc.c | 37 +++++++++++------------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 811a817c..27bcd73f 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -386,23 +386,18 @@ egismoc_exec_cmd (FpDevice *device, } static void -egismoc_set_print_data (FpPrint *print, - const guchar *device_print_id, - const gchar *user_id) +egismoc_set_print_data (FpPrint *print, + const gchar *device_print_id, + const gchar *user_id) { GVariant *print_id_var = NULL; GVariant *fpi_data = NULL; g_autofree gchar *fill_user_id = NULL; if (user_id) - { - fill_user_id = g_strdup (user_id); - } + fill_user_id = g_strdup (user_id); else - { - fill_user_id = g_new0 (gchar, EGISMOC_FINGERPRINT_DATA_SIZE + 1); - memcpy (fill_user_id, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); - } + fill_user_id = g_strndup (device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); fpi_print_fill_from_user_id (print, fill_user_id); @@ -465,8 +460,8 @@ egismoc_list_fill_enrolled_ids_cb (FpDevice *device, pos < length_in - EGISMOC_LIST_RESPONSE_SUFFIX_SIZE; pos += EGISMOC_FINGERPRINT_DATA_SIZE, self->enrolled_num++) { - g_autofree guchar *print_id = g_new0 (guchar, EGISMOC_FINGERPRINT_DATA_SIZE + 1); - memcpy (print_id, buffer_in + pos, EGISMOC_FINGERPRINT_DATA_SIZE); + g_autofree gchar *print_id = g_strndup ((gchar *) buffer_in + pos, + EGISMOC_FINGERPRINT_DATA_SIZE); fp_dbg ("Device fingerprint %0d: %.*s", self->enrolled_num, EGISMOC_FINGERPRINT_DATA_SIZE, print_id); g_ptr_array_add (self->enrolled_ids, g_steal_pointer (&print_id)); @@ -1012,7 +1007,7 @@ egismoc_enroll_run_state (FpiSsm *ssm, EnrollPrint *enroll_print = fpi_ssm_get_data (ssm); g_autofree guchar *payload = NULL; gsize payload_length = 0; - g_autofree guchar *device_print_id = NULL; + g_autofree gchar *device_print_id = NULL; g_autofree gchar *user_id = NULL; switch (fpi_ssm_get_cur_state (ssm)) @@ -1092,8 +1087,7 @@ egismoc_enroll_run_state (FpiSsm *ssm, user_id = fpi_print_generate_user_id (enroll_print->print); fp_dbg ("New fingerprint ID: %s", user_id); - device_print_id = g_new0 (guchar, EGISMOC_FINGERPRINT_DATA_SIZE); - memcpy (device_print_id, user_id, MIN (strlen (user_id), EGISMOC_FINGERPRINT_DATA_SIZE)); + device_print_id = g_strndup (user_id, EGISMOC_FINGERPRINT_DATA_SIZE); egismoc_set_print_data (enroll_print->print, device_print_id, user_id); /* create new dynamic payload of cmd_new_print_prefix + device_print_id */ @@ -1143,7 +1137,7 @@ egismoc_identify_check_cb (FpDevice *device, { fp_dbg ("Identify check callback"); FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); - guchar device_print_id[EGISMOC_FINGERPRINT_DATA_SIZE]; + gchar device_print_id[EGISMOC_FINGERPRINT_DATA_SIZE]; FpPrint *print = NULL; FpPrint *verify_print = NULL; GPtrArray *prints; @@ -1330,8 +1324,9 @@ egismoc_fw_version_cb (FpDevice *device, { fp_dbg ("Firmware version callback"); FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); - g_autofree guchar *fw_version = NULL; + g_autofree gchar *fw_version = NULL; gsize prefix_length; + guchar *fw_version_start; gsize fw_version_length; if (error) @@ -1360,13 +1355,9 @@ egismoc_fw_version_cb (FpDevice *device, * all but the last 2 bytes as the FW Version */ prefix_length = egismoc_read_prefix_len + 2 + 3 + 1; + fw_version_start = buffer_in + prefix_length; fw_version_length = length_in - prefix_length - rsp_fw_version_suffix_len; - fw_version = g_new0 (guchar, fw_version_length + 1); - - memcpy (fw_version, - buffer_in + prefix_length, - length_in - prefix_length - rsp_fw_version_suffix_len); - *(fw_version + fw_version_length) = '\0'; + fw_version = g_strndup ((gchar *) fw_version_start, fw_version_length); fp_info ("Device firmware version is %s", fw_version); From 90c4afded4e669464c866235698d16d9bd940964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 22:14:14 +0100 Subject: [PATCH 62/79] cleanup: Use non-const pointers for non constant cases We had various cases in which we were using const pointers for non constant data, and in fact we were allocating and free'ing them. So let's handle all these case properly, so that we won't have newer GLib complaining at us! --- libfprint/drivers/elanmoc/elanmoc.c | 4 ++-- libfprint/drivers/focaltech_moc/focaltech_moc.c | 4 ++-- libfprint/fp-device-private.h | 2 +- libfprint/fpi-ssm.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libfprint/drivers/elanmoc/elanmoc.c b/libfprint/drivers/elanmoc/elanmoc.c index 0261bfff..e86b79a2 100644 --- a/libfprint/drivers/elanmoc/elanmoc.c +++ b/libfprint/drivers/elanmoc/elanmoc.c @@ -50,9 +50,9 @@ elanmoc_compose_cmd ( const struct elanmoc_cmd *cmd_info ) { - g_autofree char *cmd_buf = NULL; + g_autofree uint8_t *cmd_buf = NULL; - cmd_buf = g_malloc0 (cmd_info->cmd_len); + cmd_buf = g_new0 (uint8_t, cmd_info->cmd_len); if(cmd_info->cmd_len < ELAN_MAX_HDR_LEN) memcpy (cmd_buf, &cmd_info->cmd_header, cmd_info->cmd_len); else diff --git a/libfprint/drivers/focaltech_moc/focaltech_moc.c b/libfprint/drivers/focaltech_moc/focaltech_moc.c index 190ab436..9872c7c7 100644 --- a/libfprint/drivers/focaltech_moc/focaltech_moc.c +++ b/libfprint/drivers/focaltech_moc/focaltech_moc.c @@ -126,12 +126,12 @@ fp_cmd_bcc (uint8_t *data, uint16_t len) static uint8_t * focaltech_moc_compose_cmd (uint8_t cmd, const uint8_t *data, uint16_t len) { - g_autofree char *cmd_buf = NULL; + g_autofree uint8_t *cmd_buf = NULL; FpCmd *fp_cmd = NULL; uint8_t *bcc = NULL; uint16_t header_len = len + sizeof (*bcc); - cmd_buf = g_malloc0 (sizeof (FpCmd) + header_len); + cmd_buf = g_new0 (uint8_t, sizeof (FpCmd) + header_len); fp_cmd = (FpCmd *) cmd_buf; diff --git a/libfprint/fp-device-private.h b/libfprint/fp-device-private.h index 759a678f..1c3702fa 100644 --- a/libfprint/fp-device-private.h +++ b/libfprint/fp-device-private.h @@ -44,7 +44,7 @@ typedef struct FpDeviceType type; GUsbDevice *usb_device; - const gchar *virtual_env; + gchar *virtual_env; struct { gchar *spidev_path; diff --git a/libfprint/fpi-ssm.c b/libfprint/fpi-ssm.c index c34498ab..b816945f 100644 --- a/libfprint/fpi-ssm.c +++ b/libfprint/fpi-ssm.c @@ -73,7 +73,7 @@ struct _FpiSsm { FpDevice *dev; - const char *name; + char *name; FpiSsm *parentsm; gpointer ssm_data; GDestroyNotify ssm_data_destroy; From 057c209bebdd852178030e3cae7a7bd1b1dfe05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 22:23:02 +0100 Subject: [PATCH 63/79] build: Build-depend on glib 2.68 GLib 2.68 is now more than 3 years old, so we can definitely start using it without thinking too much. This allows us to drop lots of compat code that we had around. And like the previous commit tells us, it will also help us to have more correct code around. --- libfprint/fpi-compat.h | 30 ------------------------------ meson.build | 2 +- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/libfprint/fpi-compat.h b/libfprint/fpi-compat.h index ad86874a..efb77723 100644 --- a/libfprint/fpi-compat.h +++ b/libfprint/fpi-compat.h @@ -20,36 +20,6 @@ #include -#if !GLIB_CHECK_VERSION (2, 57, 0) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GTypeClass, g_type_class_unref); -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GEnumClass, g_type_class_unref); -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GFlagsClass, g_type_class_unref); -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GParamSpec, g_param_spec_unref); -#else -/* Re-define G_SOURCE_FUNC as we are technically not allowed to use it with - * the version we depend on currently. */ -#undef G_SOURCE_FUNC -#endif - -#define G_SOURCE_FUNC(f) ((GSourceFunc) (void (*)(void))(f)) - -#if !GLIB_CHECK_VERSION (2, 63, 3) -typedef struct _FpDeviceClass FpDeviceClass; -G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpDeviceClass, g_type_class_unref); -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GDate, g_date_free); -#endif - -#if !GLIB_CHECK_VERSION (2, 68, 0) -#define g_memdup2(data, size) g_memdup ((data), (size)) -#else -#define g_memdup2(data, size) \ - (G_GNUC_EXTENSION ({ \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ - g_memdup2 ((data), (size)); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ - })) -#endif - #if __GNUC__ > 10 || (__GNUC__ == 10 && __GNUC_MINOR__ >= 1) #define FP_GNUC_ACCESS(m, p, s) __attribute__((access (m, p, s))) #else diff --git a/meson.build b/meson.build index 10d17e75..b57d3c51 100644 --- a/meson.build +++ b/meson.build @@ -21,7 +21,7 @@ datadir = prefix / get_option('datadir') cc = meson.get_compiler('c') cpp = meson.get_compiler('cpp') host_system = host_machine.system() -glib_min_version = '2.56' +glib_min_version = '2.68' glib_version_def = 'GLIB_VERSION_@0@_@1@'.format( glib_min_version.split('.')[0], glib_min_version.split('.')[1]) From ed1815c3d951630c472f093ed8dfdcb03e597134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 20:28:01 +0100 Subject: [PATCH 64/79] build: Allow testing more drivers in both big and little endian I've tested them in a s390x host and many more tests work fine, so let's enable them. --- meson.build | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/meson.build b/meson.build index b57d3c51..64779e9c 100644 --- a/meson.build +++ b/meson.build @@ -142,8 +142,26 @@ default_drivers = [ # FIXME: All the drivers should be fixed by adjusting the byte order. # See https://gitlab.freedesktop.org/libfprint/libfprint/-/issues/236 endian_independent_drivers = virtual_drivers + [ + 'aes1610', + 'aes1660', + 'aes2550', + 'aes2660', 'aes3500', + 'aes4000', + 'egis0570', + 'elanmoc', + 'etes603', + 'focaltech_moc', + 'nb1010', + 'realtek', 'synaptics', + 'upeksonly', + 'upektc', + 'upektc_img', + 'upekts', + 'vcom5s', + 'vfs101', + 'vfs7552', ] all_drivers = default_drivers + virtual_drivers From 6768bd0ff4a975ba23388c9add570140ba681e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 21:12:14 +0100 Subject: [PATCH 65/79] egismoc: Use strictly sized types to hold check bytes contents So we are sure about the size we're sending at compile time too. --- libfprint/drivers/egismoc/egismoc.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 27bcd73f..c33809e1 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -273,12 +273,15 @@ egismoc_cmd_ssm_done (FpiSsm *ssm, data->callback (device, NULL, 0, g_steal_pointer (&local_error)); } -typedef union egismoc_check_bytes +typedef union { - unsigned short check_short; - guchar check_bytes[EGISMOC_CHECK_BYTES_LENGTH]; + guint16 check_value; + guchar check_bytes[EGISMOC_CHECK_BYTES_LENGTH]; } EgisMocCheckBytes; +G_STATIC_ASSERT (G_SIZEOF_MEMBER (EgisMocCheckBytes, check_value) == + sizeof (guint8) * EGISMOC_CHECK_BYTES_LENGTH); + /* * Derive the 2 "check bytes" for write payloads * 32-bit big-endian sum of all 16-bit words (including check bytes) MOD 0xFFFF @@ -290,26 +293,22 @@ egismoc_get_check_bytes (const guchar *value, { fp_dbg ("Get check bytes"); EgisMocCheckBytes check_bytes; - unsigned short value_bigendian_shorts[(int) ((value_length + 1) / 2)]; + guint16 big_endian_values[(int) ((value_length + 1) / 2)]; + size_t sum_values = 0; int s = 0; for (int i = 0; i < value_length; i = i + 2, s++) { if (i + 1 < value_length) - value_bigendian_shorts[s] = (((short) value[i + 1]) << 8) | (0x00ff & value[i]); + big_endian_values[s] = (((guint16) value[i + 1]) << 8) | (0x00ff & value[i]); else - value_bigendian_shorts[s] = (((short) 0x00) << 8) | (0x00ff & value[i]); + big_endian_values[s] = (((guint16) 0x00) << 8) | (0x00ff & value[i]); } - unsigned long sum_shorts = 0; for (int i = 0; i < s; i++) - sum_shorts += value_bigendian_shorts[i]; + sum_values += big_endian_values[i]; - /* - derive the "first possible occurrence" of check bytes as: - `0xFFFF - (sum_of_32bit_words % 0xFFFF) - */ - check_bytes.check_short = 0xffff - (sum_shorts % 0xffff); + check_bytes.check_value = 0xffff - (sum_values % 0xffff); return check_bytes; } From 59dc585ccd070ef94ba7a5dfe173a803b1dfd41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 21:41:44 +0100 Subject: [PATCH 66/79] egismoc: Simplify check bytes computation We use big endian values for generating the check bytes, but we can do the same logic in a simpler way. --- libfprint/drivers/egismoc/egismoc.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index c33809e1..960df140 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -293,19 +293,21 @@ egismoc_get_check_bytes (const guchar *value, { fp_dbg ("Get check bytes"); EgisMocCheckBytes check_bytes; - guint16 big_endian_values[(int) ((value_length + 1) / 2)]; + const size_t steps = (value_length + 1) / 2; + guint16 big_endian_values[steps]; size_t sum_values = 0; - int s = 0; - for (int i = 0; i < value_length; i = i + 2, s++) + for (int i = 0, j = 0; i < value_length; i += 2, j++) { - if (i + 1 < value_length) - big_endian_values[s] = (((guint16) value[i + 1]) << 8) | (0x00ff & value[i]); - else - big_endian_values[s] = (((guint16) 0x00) << 8) | (0x00ff & value[i]); + big_endian_values[j] = 0; + + if (i < value_length - 1) + big_endian_values[j] = value[i + 1] << 8; + + big_endian_values[j] |= (value[i] & 0x00ff); } - for (int i = 0; i < s; i++) + for (int i = 0; i < steps; i++) sum_values += big_endian_values[i]; check_bytes.check_value = 0xffff - (sum_values % 0xffff); From 5462db9901b6540ff46cb9e3b14628cc57c764de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 21:45:53 +0100 Subject: [PATCH 67/79] egismoc: Convert the check value to little endian In this way we can support the big-endian architectures too. --- libfprint/drivers/egismoc/egismoc.c | 2 +- meson.build | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 960df140..841e7e93 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -310,7 +310,7 @@ egismoc_get_check_bytes (const guchar *value, for (int i = 0; i < steps; i++) sum_values += big_endian_values[i]; - check_bytes.check_value = 0xffff - (sum_values % 0xffff); + check_bytes.check_value = GUINT16_TO_LE (0xffff - (sum_values % 0xffff)); return check_bytes; } diff --git a/meson.build b/meson.build index 64779e9c..07019d3c 100644 --- a/meson.build +++ b/meson.build @@ -149,6 +149,7 @@ endian_independent_drivers = virtual_drivers + [ 'aes3500', 'aes4000', 'egis0570', + 'egismoc', 'elanmoc', 'etes603', 'focaltech_moc', From 3e5ab6fdad95666cd998c52be438f767ee8440c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 21:53:41 +0100 Subject: [PATCH 68/79] egismoc: Convert value check values to big endian only when needed Since the driver seem to require a big-endian value it's just better to use architecture native endianness to compute the check value and eventually just convert to big endian as the chip wants. --- libfprint/drivers/egismoc/egismoc.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c index 841e7e93..0b5e8d2e 100644 --- a/libfprint/drivers/egismoc/egismoc.c +++ b/libfprint/drivers/egismoc/egismoc.c @@ -294,23 +294,21 @@ egismoc_get_check_bytes (const guchar *value, fp_dbg ("Get check bytes"); EgisMocCheckBytes check_bytes; const size_t steps = (value_length + 1) / 2; - guint16 big_endian_values[steps]; + guint16 values[steps]; size_t sum_values = 0; for (int i = 0, j = 0; i < value_length; i += 2, j++) { - big_endian_values[j] = 0; + values[j] = (value[i] << 8 & 0xff00); if (i < value_length - 1) - big_endian_values[j] = value[i + 1] << 8; - - big_endian_values[j] |= (value[i] & 0x00ff); + values[j] |= value[i + 1] & 0x00ff; } for (int i = 0; i < steps; i++) - sum_values += big_endian_values[i]; + sum_values += values[i]; - check_bytes.check_value = GUINT16_TO_LE (0xffff - (sum_values % 0xffff)); + check_bytes.check_value = GUINT16_TO_BE (0xffff - (sum_values % 0xffff)); return check_bytes; } From 5501dc7b4753c01b4c03bdc9f82fad1b98b63e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 20 Feb 2024 01:00:29 +0100 Subject: [PATCH 69/79] build: Stop using deprecated dep.get_pkgconfig_variable() method Use generic get_variable() instead --- doc/meson.build | 2 +- meson.build | 4 ++-- tests/meson.build | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/meson.build b/doc/meson.build index 77236a06..e48067fd 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -19,7 +19,7 @@ content_files = [ expand_content_files = content_files -glib_prefix = dependency('glib-2.0').get_pkgconfig_variable('prefix') +glib_prefix = dependency('glib-2.0').get_variable(pkgconfig: 'prefix') glib_docpath = join_paths(glib_prefix, 'share', 'gtk-doc', 'html') docpath = join_paths(get_option('datadir'), 'gtk-doc', 'html') diff --git a/meson.build b/meson.build index 07019d3c..984f26e6 100644 --- a/meson.build +++ b/meson.build @@ -269,7 +269,7 @@ if install_udev_rules udev_rules_dir = get_option('udev_rules_dir') if udev_rules_dir == 'auto' udev_dep = dependency('udev') - udev_rules_dir = udev_dep.get_pkgconfig_variable('udevdir') + '/rules.d' + udev_rules_dir = udev_dep.get_variable(pkgconfig: 'udevdir') + '/rules.d' endif endif @@ -306,7 +306,7 @@ if not udev_hwdb.disabled() if udev_hwdb_dir == 'auto' udev_dep = dependency('udev') - udev_hwdb_dir = udev_dep.get_pkgconfig_variable('udevdir') + '/hwdb.d' + udev_hwdb_dir = udev_dep.get_variable(pkgconfig: 'udevdir') + '/hwdb.d' endif else udev_hwdb_dir = '' diff --git a/tests/meson.build b/tests/meson.build index efa573fb..f68ed406 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -339,7 +339,7 @@ endif valgrind = find_program('valgrind', required: false) if valgrind.found() - glib_share = glib_dep.get_pkgconfig_variable('prefix') / 'share' / glib_dep.name() + glib_share = glib_dep.get_variable(pkgconfig: 'prefix') / 'share' / glib_dep.name() glib_suppressions = glib_share + '/valgrind/glib.supp' libfprint_suppressions = '@0@/@1@'.format(meson.project_source_root(), files('libfprint.supp')[0]) From bebe8565cd7e2c89c0b0c5e6ee7353b80d6a51e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 19 Feb 2024 23:39:50 +0100 Subject: [PATCH 70/79] Release 1.94.7 --- NEWS | 15 +++++++++++++++ meson.build | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 103740f6..44a048f8 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,21 @@ This file lists notable changes in each release. For the full history of all changes, see ChangeLog. +2024-02-20: v1.94.7 release + +Highlights: + * synaptics: fix enroll identify problem after user reset database. + * synaptics: New PIDs 0x0173, 0x0106, 0x0124. + * goodixmoc: New PID 0x6582. + * build: Do not require bash to build, only posix sh. + * fp-image: Simplify minutiae detection tasks. + * GLib 2.68 is now required to build libfprint. + +New drivers: + * realtek (PID 0x5813). + * focaltech_moc (PIDs 0x9E48, 0xD979, 0xA959). + * egismoc (PIDs 0x0582, 0x05a1). + 2023-08-17: v1.94.6 release Highlights: diff --git a/meson.build b/meson.build index 984f26e6..435827ce 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfprint', [ 'c', 'cpp' ], - version: '1.94.6', + version: '1.94.7', license: 'LGPLv2.1+', default_options: [ 'buildtype=debugoptimized', From ecc33b5cc663f33ad036f6d0c046d197b65bd4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 20 Feb 2024 07:00:07 +0100 Subject: [PATCH 71/79] fpi-image: Add docstring for FPI_IMAGE_NONE --- libfprint/fpi-image.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libfprint/fpi-image.h b/libfprint/fpi-image.h index e5680aea..bc0abaaa 100644 --- a/libfprint/fpi-image.h +++ b/libfprint/fpi-image.h @@ -25,6 +25,7 @@ /** * FpiImageFlags: + * @FPI_IMAGE_NONE: no flag set * @FPI_IMAGE_V_FLIPPED: the image is vertically flipped * @FPI_IMAGE_H_FLIPPED: the image is horizontally flipped * @FPI_IMAGE_COLORS_INVERTED: the colours are inverted From 619827ac7a863263128a0961196f345b143a0536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 20 Feb 2024 07:02:25 +0100 Subject: [PATCH 72/79] tests: Use FP_DRIVERS_ALLOWLIST variable instead of old one --- tests/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/meson.build b/tests/meson.build index 37b4035d..440db9ea 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -504,7 +504,7 @@ if get_option('tod') continue endif tod_test_envs = tod_envs - tod_test_envs.prepend('FP_DRIVERS_WHITELIST', tod_driver) + tod_test_envs.prepend('FP_DRIVERS_ALLOWLIST', tod_driver) tod_test_envs.set('FP_TOD_DRIVERS_DIR', tod_driver_info.get('tod-dir') == meson.current_build_dir() ? meson.current_build_dir() : From 11ad450af17f911d9711a61a8a9821f0b4a6a529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 20 Feb 2024 07:19:51 +0100 Subject: [PATCH 73/79] libfprint/tod/build: Require libgusb dependency on pkgconfig We include the directories so we definitely need to depend on it at compile time. --- libfprint/tod/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/libfprint/tod/meson.build b/libfprint/tod/meson.build index ff59d1f8..7e680b48 100644 --- a/libfprint/tod/meson.build +++ b/libfprint/tod/meson.build @@ -70,6 +70,7 @@ pkgconfig.generate(libfprint_tod, subdirs: tod_subpath, requires_private: [ versioned_libname, + gusb_dep, ], variables: [ 'tod_driversdir=${libdir}/@0@'.format(tod_subpath) From eb8ce585c6d281f2bf5b623c2ae11bcb86f153f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 20 Feb 2024 07:25:35 +0100 Subject: [PATCH 74/79] tests: Add test files for tod 1.94.7 version --- tests/meson.build | 1 + ...evice-fake-tod-ssm-test-v1+1.94.7-x86_64.so | Bin 0 -> 127896 bytes ...ce-fake-tod-test-driver-v1+1.94.7-x86_64.so | Bin 0 -> 45352 bytes 3 files changed, 1 insertion(+) create mode 100644 tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-ssm-test-v1+1.94.7-x86_64.so create mode 100644 tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-test-driver-v1+1.94.7-x86_64.so diff --git a/tests/meson.build b/tests/meson.build index 440db9ea..7dfa9b8f 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -451,6 +451,7 @@ if get_option('tod') 'v1+1.90.5', 'v1+1.94.0', 'v1+1.94.3', + 'v1+1.94.7', ] foreach tod_version: tod_test_versions diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-ssm-test-v1+1.94.7-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-ssm-test-v1+1.94.7-x86_64.so new file mode 100644 index 0000000000000000000000000000000000000000..d78474ca4137fddded971cb73105cc5918e35e9d GIT binary patch literal 127896 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 UX`{o*@P9t2InN3J0;Ki70E!p-WdHyG literal 0 HcmV?d00001 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-test-driver-v1+1.94.7-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-test-driver-v1+1.94.7-x86_64.so new file mode 100644 index 0000000000000000000000000000000000000000..3692cfa6464558490de6704cf055291ce0fd57e5 GIT binary patch literal 45352 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 L++ZQFB$fXL;~;R} literal 0 HcmV?d00001 From b972866b25b8c42dcc459eca51373c8e3905cd5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 20 Feb 2024 07:47:12 +0100 Subject: [PATCH 75/79] tests/libfprint.supp: Ignore bash leaks --- tests/libfprint.supp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/libfprint.supp b/tests/libfprint.supp index f2acc71a..07676633 100644 --- a/tests/libfprint.supp +++ b/tests/libfprint.supp @@ -24,3 +24,12 @@ ... fun:g_thread_new } + +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + obj:/usr/bin/bash +} From 72922217fab90c8cfe1329544e597fcc2c988430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 20 Feb 2024 07:52:11 +0100 Subject: [PATCH 76/79] tests/tod-drivers: Update binaries as per a test leak fix --- ...vice-fake-tod-ssm-test-v1+1.94.3-x86_64.so | Bin 131584 -> 132072 bytes ...e-fake-tod-test-driver-v1+1.94.3-x86_64.so | Bin 44968 -> 46744 bytes ...vice-fake-tod-ssm-test-v1+1.94.7-x86_64.so | Bin 127896 -> 132072 bytes ...e-fake-tod-test-driver-v1+1.94.7-x86_64.so | Bin 45352 -> 46800 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so index 31108c256f25046f0cfa3a9bf408012d66e4adf9..1957d0f22c02231bcaee3237c8e1226b39e89ad7 100644 GIT binary patch literal 132072 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≻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 UX`{o*@P9t2InN3J0;Ki70E!p-WdHyG diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-test-driver-v1+1.94.7-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-test-driver-v1+1.94.7-x86_64.so index 3692cfa6464558490de6704cf055291ce0fd57e5..680eed2e38fb04ffb025f4af1a7ac3826af70a9a 100644 GIT binary patch literal 46800 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 L++ZQFB$fXL;~;R} From bfb427267465d5dfa3115faf509e42623e30aec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 20 Feb 2024 07:57:58 +0100 Subject: [PATCH 77/79] tests/tod-drivers: Test tod driver 1.94.1 not 1.94.0 as it was not released --- tests/meson.build | 2 +- ...evice-fake-tod-ssm-test-v1+1.94.0-x86_64.so | Bin 125168 -> 0 bytes ...ce-fake-tod-test-driver-v1+1.94.0-x86_64.so | Bin 42912 -> 0 bytes ...evice-fake-tod-ssm-test-v1+1.94.1-x86_64.so | Bin 0 -> 132024 bytes ...ce-fake-tod-test-driver-v1+1.94.1-x86_64.so | Bin 0 -> 46696 bytes 5 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-ssm-test-v1+1.94.0-x86_64.so delete mode 100644 tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-test-driver-v1+1.94.0-x86_64.so create mode 100644 tests/tod-drivers/tod-x86_64-v1+1.94.1/libdevice-fake-tod-ssm-test-v1+1.94.1-x86_64.so create mode 100644 tests/tod-drivers/tod-x86_64-v1+1.94.1/libdevice-fake-tod-test-driver-v1+1.94.1-x86_64.so diff --git a/tests/meson.build b/tests/meson.build index 7dfa9b8f..39066e2b 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -449,7 +449,7 @@ if get_option('tod') 'v1+1.90.2', 'v1+1.90.3', 'v1+1.90.5', - 'v1+1.94.0', + 'v1+1.94.1', 'v1+1.94.3', 'v1+1.94.7', ] diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-ssm-test-v1+1.94.0-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-ssm-test-v1+1.94.0-x86_64.so deleted file mode 100644 index f430aeb1422399ec035924ffa5068a0b1678be9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125168 zcmeEPcYGC9)4ntXbRn-81#47L6zQN8kx+s`X;PF|!4M!ofD}_G3L1z}Lb!+uh#e6X z6%aeD0#VRl1+jqzP(c?#MQpL=d!CuCHwic3`~4?B&1C27oO#a7oH=vO?!CD&p-->s z)vAS@e>FlEhj>+dwsPX_h66jjd*VYKLdS&SLybdc$-Oo0dnzK<@yc~*C`PZ@TP^+? z#~=6?CqG^K*k^j}P`{k&bMOS|;FDF(p?eb>yl;hH|@3 z)%$lf57GmA8FFZCRUaZo{=bYJR6yf|>kc;*DYwa%i=>ThIfqgQ{|(xcGx|vM4Y1`x zG4?Ob48GNJa-oj+pZyYlOdfvJ&J$iI{Q7E-E5G0O z*$Zp%YlZ(VfZ%Zzw498NE8?dCWJUaD0P5Jp-`$zGzal=TXhr?--Z*~;_V4x3e?|8{ zRY^aWRD%EEO8Qxfek#J*ppyPit)!m~mGGNX($C8nUqyOXS8{*7O6=dM61~GJxqoOS z_UTZGzrM!(72%v#Nk6|;f@gjuc$QUS2bdughyNr0&8-B_INVqf&csUe?y1DieZjA& zALhS`_`g)r&#{%<-?tL`%&w%LNw~ivy?GdKMf2imEbR_WV}NlQ<435gMVD8Ug)Ey zs^ktOp$USgUZ~_;Wz@I%p`@g-X&LEB**VEsIY~*Oq~3%3CXG(XN*SA)os*I^xNnb% z8R;p5lSfWW(YA_Pl15EQP8yS%o;)%2x|GnEOms0RbyP~y*p!^4oT-^9p|L`eMvY4u zH6dx##N_Pkq*2M)IYEAEI$)$jEHyhR8NH+?WoM@)jmb*!4({QgDk8ScYg!Pt8d}e<@jjSJYF|sI<)V+%&ht^pwd-V{#`>bQv&r zP{Sztb0WfFV)9fBniffc9h1|?szK-wqHso94!qkUZo?2$M&&po21Mbi5XX;3Xt52F zZF?!14$Bxho{~W9T_leloisTm8L^Zh{`6XM)3Z{>c%$OvK%<&xVoLhhoN;brddid> z+kj5QdMsz)5Y9pOd(TJ~h?g&a98U&*S|+0FJX(b^m-i+G~Wauo2%H|hG@c(1uT-_QA9{xG^JJ(xUDxZ6kqva0$ zvCYSKWZ1IH$CnL`Eo1rkZEPON0Uy7;kKeGB>ZMNoO&jBUe81hB`uL8ADACHtS1)Tn z9ejM-l+qCI8kvKhDR8!`*+GK7Kuy8wySF z@!i@4?Rh@F6GIf6<>Pa|>HM4LaKK=+FpL+!7-#8z?xx)m_^zmEx_)~oRmOg%-kI#JT z{F~+Dw|1DI^L+faKK>#fznzc2*vG%X$6w~-U+Ckn^6@+P`2YU;uLb_Kz`qvw*8=}q z;9m>;Yk_|)@UI2_wLmEJWBt>9Pt4z6JF%$NyY;Ju5(|oRs+Vm^%->jhgP5nR{TE;# zENk*5Nd430{&8$MxcBq2va)%UAIAfOgL~g~uX#{#aPQ0RH4g|5?tRL==E1oi2J}sg>CE|K`#PyJf>%I}!2@%&_BCgveZ+P1i0dX1*QZ8Y$3|Qm5!baMuK%hV;eY2k zG2G=h{GJ%Db&#ZEX-+JbvR`WJQdXyISN+rTF#AFqY>N9|m;2klLUQikiG>FeU;eIZ z;>)Eq605zI`0n31$D@O{?G9>}?HVKV)RE_Um)E5LvZ35&LlX15JdQg2NB6ANEqOJd=NiG?NB6eOzGots+Wrfq24#!jubt%i`A`C%wz{q-A%x4~)x8?g)32@jgJQBfrp z?%-fwZ)*R>>?pf1ukw%jY!q%DJ|??(_wu?)*>efh~*>^Y_@MrEbLx8I0@n< zD-`B#Nh}<)2}Ams2}<3p zQr{zR5!q)`>MR}7HcI`<9_)B6iI1@@FHOh#hJv}sAdsQd)SLD3}@%|?m)~!0MhB~bNAl9&7peKK0tDt$~3a9Tc%mvsXjF-Hc%mKN(iuzhb zL-zN^0uyfkASXCXF-=Wzz?v+HP260Hy9x(1PAuxHc*C*1`jfZ|58IbPlgElNs6XK?sq(@neXsRWa*b34_zoVx6MF1JjgPc^nGFx&VRgR>}jGshhD@SF0j61^du-Z#g*GFo- z>(L{u*HBe%-v*V)Z&dm1W&z)rDqH_3DqZWn3zb3Z#Y8U6B?$-64?2qd8$GP!J|Fqy zLv(7rM_&}xYK#}5p}Gf}J!3OkLX5D}I4b6VurZ3LzK{;uU#Y0(vY(TAOBlD{_Eh>4 z_T_4=B?4CKPJkVlA%{RY)`zkcdv8vZ;aFPULEn)4SVcbu%PPZ;=rL!@O#Xn`@|o2D z=o;Rp_&0n6{NF47JKqsIyaQrgEgBU6V+fCO_^&${fj_bUvYyd}-V(E|9{?Sa7OX9h z6z!Zx;UO@Qq~eZ(z3%}D#G=?Gf6>;Go1Xju0(N*dptp-4)KM3e>oIJ&;S>(z4;{vq zw`CYda~L(L3!)~PLhHU?w?c}WYkVEPf;&uUuzS2*dwc_Rlliyyc!T!%9EkO_${uj0 z)^Ol-9K022$5}+;O!)8+q&l(V z4>IJwSh`@|qS&kcbnQ6yfEYt!@=X~AgzQnb72g{_VS<_16mgQxdQ0rM9>jXdCz@&7 z)Um(-6OmMN!SIyF|BXI8orBb_^zF4 z+YYgYe*p!UUw5kNUC4vxNve9VsxAVt7JUazSi@b!7{yZX3X8>SNJ5aIS_^6q&?Gu5_jlW40?ZKrtJ#1^OMU|r1? z#(BUVr-mQKc>wWth;i;m|A_kWs`+=U%4Q4Ie2r>e0AelK=O1T`UB&CXFkYn)U$H`1 zoU1}3C^Ve*FQSX$-Ds2xFB%IZii~{rz?W~(!`Iku!n~)khc|V=G3-U6QT9W%6l*^U zwNas`RcJ4W^%boWhFV0)=@mue0u06f?*(#SoeSD!Zu4QL6H85bM#s zu1btsH`>pDCkbyg)D}aA32!yj7Bk@$BfS-enDE|2{$|2k4bEywcwA3*yc{Lr4OL)W zB$kXDuojyaE3h|S6p`qpU%R$I{3A2S#_miN+CZTM6{@d7 zy(rYfc9lOfa9F-|YJ!BIF@rty(jMN%Q$=Kkn(W~zwMPsl`aDj-3z%!dI$JkmI&z8z zD)Ys_>7VrbBbOj+J6ADXQ6zWVKox~pb;7Z^m?BO|UH}x7v6&PzIfKC z&K`zp4;N?;Y3$*ouS6}wQevYga#XocjD>;xp;&(X=_f@_LLR{6(UU*Y%!P>bl7v}M zN`vNMsT^uAOG`OeU*w#uF)OqvN=k}cLiE7?ZP0|-CxT1S~R6;Y10sZBUQc zU#q|_Ag~k#cBulJ0b&PNK&-jDTwC~7zNRWvjY3mZ zC{~5~P^dQz23Pr0j9hpr=30p{V?C0+d7bv~-b-SSy6oWvwFgs-MAPSp0`w4E?VbiU zUy7NrpO0iBt6Yl(RAE63kCBR$z@@jaf6%^6WrYQ`eXZg4Uok_Xu?@#wB#rjER?v_) zmK4N+FgnpHoX*0@8M1Q)TXwD>^AI)ZaEuhDKTcxK_yqadEKnP4eo<_26mGB@?1U~^ z>0H<$!bT((G=&1-Xa!>95Kd>|5C>a!t|0ReI6iDV$Zh#+Nu+F^Df}p5 zdxl~@O2W-}01Kpfi(MVa~MHyCK6al!2l2&xo|oQhgM|E z&J|=H!iu*-A*?t}5g9}@TM<=LL>-7Ip**5CJE5N590~=%F#^QKA)L;_Ar7|eTtVg` za2%~T8Y>P~aAuL>n7T$RvkJso`zds3RQbL68rb5^af&4qL{mZ?V<{&_V?Urc-zKeC)oTHdu#Zd{o`6q}~$HN>$?eOMNG-uG8 zQ`l6JkY~e|%gzaTIRkg5a-b1d{g&$If#xcS@!a z2p5N0&^=Fei^MgJgcHRztDzbe`BpN4!TQgkI25= z)~fqEW zvWd5CQ`JrUBj_HsIY#w2rvAHBe?!&ZpZbS+`u(;^0b6WiVV)eQrM9s!Pa=+wrz3yT zHs0~^1mQ%9*1C#~IS^InEUc{N-HL76vtpZPK&;}AT-y+v?3v`WEki=u7(lsuRj#MX zT~E0wjB+)gQ`21JyY|YOyCq1B+U(~(?dQ*DM1NcMvrBD*vPMe|B(jlMmdaH&+ki7r z+3?gWva+csHH-QeGGBz-AHS2#5^Ds9NB8s zqkRG%Ly9j+SZpsen(m_?yzYZ4fLN{mDMWTNmn(?j3SvHp_2B!ife3;TGnX!7GF=9( zt8A@RP2st!HA}TNpw<YKW&-2<(JMCuz`)Q~iVIFrT zN>@ZgxjY^NWBKxUe);p*-Yiyp=ds_>>Uti#2b04&Uios#|&STx=@B?9ljgPT- znJ+8KxwsJ%>s8)Akj*&h3mnSY6RD~Z`ZPL1tlS7J(3D9{8&p$c)pRvAjoSiElGyb` za43S2Hh(;pKa^d=^`_7}l)Z-CmOqr; z!2E|iRG)+O9m>ht1iRDlRb89<13t`JQ{u_!%kjOZ<9qpWu}~?9RqGvS(h!pqA@RSv zFxe`XRq>(Vr|1zXr;7cIkFf@r@2kpwsR5(#WtcL}Eu#9?vF$A2^;}sU25xJbReQCIPD9 zmg6)MdJx-g#rDE78U1%4)*o+rqh}26$*=$9RP`OzRz6HyNxkpv@RF&6LD zokErhO;Vv16ndNnJM{7FR*40p7&+M7t39;S9wxAdh8lqG@oaa5MY&|t00ugAvgyO+ zm?tOp8wfwp%uF`C4!6ICNf8cyoP+#_N#>hJ#Lo|iQ;mik*N-}`VQ9gMc?XEK_;qN~ zBqPtf=Mm76CYi~q@_4bE@h~b1^Cwlg_hC^PN0rSzm2PNt1g|Pd=5pl9V3O%7oG78S z1L_g;8x+{F1opE68=$~$0kP(9@@%nA>O8pOMjFKD3MqQ+BCx6@MUtxDs38`{!f2MN z`gfO#U20Q(tWQ0IpcaF`o#lslayCbQDct_O2St5dsz1?F@85jIfi2M(<*1?q@*AVkJE}NaI8mbU zRUp7Ld0b+^Xh3Xrs*7nZRBY)W*7X~0(}ZJbDyPmE$hQ9KHExR5)njo4PhLj5;qwnz zluSdrvlh!xPp8Q8a*oy$4iT?<6_8OY;z`q?R0yd=*UdLxmH?o4-CF z*!rk96Nv3UiY*l_2=v7u){0kLZ>C`sjyG>&+YEYI4-wb>Vjl=ir@6w$FG!_ktaf*j zc6T{NcBlx48Q6A;c-0R^I-@Vx)zV2?NM$FgvK99W_T3=X4=>x;z2navG_|`-*jlT) z^L!PZGX7O-18QxkS}#C}3?r`7Mxo51!~iW=(83hKdok#T}6#cbh3@#eXT?V2SV zzA+hfjM+%B-E*HLJO|8{VUsKao139_rq6uS80ogoROu);L?O zVW0(bt0#zcIY8LuWN}G+V}t~n^n~Pe8XUvf!CQQ1G;*FNH&*?f{(1^#gZROih74#n zQ!sh=2$&Z@tc~kDFwSmv3wU14LWUWrf|#|CJc2R%p>U$aY%cU-KE0t<`V#5IJYRu* zeYXHRjli1ufO$`3KGV^xq)-bLdRm3{f>>X9{;ur6q@MOLkv+839x}CuN7=(*bxw?0 zA0Gmc2SIaH=t2s$QlZW<rNostxN)`pcN=YrIl{wCbs*b8sF~ zTauk3o*;A-LXP*A@kxwr#;VpS`|cF)Ef!Z9uP?!PFV*o5M+@R)K8W=oK5b`vPXOLV zZ*nnR4VA^VN^hs*DwT8dp%|WvcR%mM0v7RF4&tlO)TTLOJ1 zoLO!{55h3L3C%C180w$VR_kJ`NN4UcOwVQPvlSf;`inR|VU_U1?H4frg8ex$7m=-p z$(Q3=3@P)=p86FWCZ{4l(U9I@@|D7gV#r_a5D$MM?lfxBCaG$ZacIG$e*nZ<{yacw z%3fs$`ubY%sa1onFWdlo$=K3pJN<@@srERWj3o0EB8-DgID)UrN6-GN;OZ&3u_zJD z34 zsK>mBm0V!FgXP!ERA6lsSSpA$={e78UeVH2M{^g2vQ+4=+hjEDDAbx3RsT!Ta+Y&Z z)X*A&4m)nhjd5A=HSBzw4$PHRWx4}y$NDX zQL{;B-sE^%h5kUfXy&L;k_s)P&>i$@7}1GHeM?i4I6ehIJrA%f|7XB25LEszR^bFm zjGQ3jrJ%w3!D=1-ia$X=kYJLJo%{_{P6n+<9LE5>M&Jx8kq8vweFUfx`0KO8YK-7e z_`)BS%lY%f&~JiRLpgq5RV@K(7d%xB(ADS&mi|!V;1^U1=1kReo@z>_ru1i^NoP;7 z6QI|D=LM)eFDeMo+c6_Kt-S!v6Hb%>JzaqfQ((ghEMI}$cB`0R6NvS}(;hG{1fG-- zHkwlCCKVc{LU&MT9u01nx39~hrEVJu%a;p*wj6p_Z$BxnH4?F^ zo42ZcE>w->vWz%dewv6sNKP8|2*%{|0E3CjXWbtQ)-AuYyeC=o87 z>~GALu<1Tmx5RY>`i7TKQPe+4$&gvZSVs{!YQhDl~W`v`zKlY8K}l2 z7$-4ev?jI(6nbuagv5hWcv}Y6WwN z0-G~OjJ5&9+VZ3a%*)apOHaF6#{wY=8M%AZM z{UlHQ2-!%-%dyjqU`y1`LR#ai+>H8J@-}$jUBWvtlNj~h0X_HYQIDM(E4Dgnnysid z%%>IG!a_04+aT7*%LAry6V(?h!4EuA(LJZVLM;N!s(gt10qB5%2CISk62uw>@gD_| zNDxDUAQ&;-sUQB)oNsHb>JIi?)%u#KFg`-jV4AA+o>?;VtsvIUC)}a?-C}`V@#0bc zrkMcZ|2X%bn0UbjqYWa>tfZ~np$60tv}x^RydzpJ9=9EUg{3Ec*(-#IRG(E9t%+!@rw9Wn3MF7 zg~DFHVI)yFQ9SfD5WpfADYgYzuFY2z+kX|?JP_-?$6SjLo2(l0Y43I_H=J@CRqhOx z8%jBe_o|$rPnK{sV%g8D+Ryd^u}uv7S*y13pP=tXd@!{KKI;7(aC*DbO49m1sW#-} zObDp~wufg^g`L(*Bzvlt^#*+YP0~I-|3*`|aVeQ~9J~0(Z;KDdc;SiTBoK{eDEiGU zYKhG^i6xH04OW9?t|uJiAtL|B8GZ+pK>hV%bz?b72=iT4KS0&r0%Fa7)K%{f()+-! zN``+9u^-Iv4+$qqkakyW=SXB3F~qi2u|1nFrr8H#l|JHPqiNg>A1dduBg*h=p#uiW zQUevCAThTqh?WXs3_)ZEK`?iWpv#Ul!(XCWcZ&)mmRdhnt!rk=&<}uEe?9CD-S3v8 z0H)#$AN;b8(=hFV+5K4IL~+X#Pz{UBllU;EqOLPPQEaCww!y@f>|vu@*2of9$a;My zN|W{Xf@e*q)tuLc*_4GPK}-4SuwGzcxSSzqKikBK^`78lJfd&>`C^6)$2PO;sO(#qVU*t#pWsUX(P54jczE(@n>NQ|Kz z|D-iMT36+|QSM@ztt!jH2-SZY`?0j2S8f#B{03q@qPF3(;CSS1L`S)0;eO!sEerYk z%RPnoi0gYH@&ezjz?_}No(mHe<1jlLQQ>T1^QkqDZMe@(Fb|LsHkP6GGk;Z-rzpxn zynmP8zu4Jw%H|$X&rYgZ=*6QW_~RN)J{MEdZ>s6(8^j)aL9AaMM5p#n9D6etb`lmq7P3apU=8$e*g9bnUVP4UeQI+=GrVf~^vt z#+N9i&1yA7)0L`eE;Ze=*fp&LND<8H&3t?QRdwcPsmfbbWhPZtSCzY`$>2_=%BG&m z#f~@IfmfAj-v=`$IPE(LCyF<>Lp^Mp};OvU^79iTOV+30Vg>xQ=F@# zSwf*&Ds+Mh^`+3IG#I>!k+{#M<-+@%V3w*K!5)s%9^SuR>~RcxSgrQJY}YyZ1)N`Q zsy_p4zNtQAKfj~Oe*lZG^0xMJMJ_BD@HZhT!JMDP{z3b3!740B^tIx<$(XXx*p_22 zl16(z7YyZ%_@FunW*4THa_~WQaO8vPp!lFVejeh(g4bZAFnt4wIb#m0K=VIpgDuyI z4UWbQ*75g4mu~O{JrGqRY{X>2I4EEl3S#3B4nDL94snp;LyP!%2pk_m8*pTc;f344|Hj0`Ca-7*y7S9z97u#oce|DyK(+92A3B{L-b6-8!4?o3hj2Ozhd9`>a|M})z;TA+ zxL9%YCyp}}#~qWzQg47*AKm5RpedX+Wf$1u;caA=6zl@QQp-dpoX*1Gq-6`dOCsL= z3ZX>r%5C{;NyNMF2tSH<(*Oy!dqt!29+U;a5@Wzrew>`6=WX5lpjJlU~Q0@Vbmn7^AuL1!nzT}nzM+Okng-8EV^_m zy-b&ul2K9+LWO7J5Kd>|5C>a!t|0ReIQl7$35sLd4B%*{IEr(`JimfiH9Z`Dml|M; zOKU?Fu*87aE)`B^;W(3)t#B)exV1iJ8r>?l=C36YxBi+>eiXMZ1T5HZpTxhhF%P)U zSKPG~cUR)>b0<}9Y!#Talj&>Mr}?GTa=!2`#k38HRKID+9*%Gh^P7f#%od2n3h@j= zY^@Me(1PvWy&%?O3l$IQ%X?4{UL92gMQ(0%6Wt;Z*)jL#+_gbg$gz`=+6W!jIzK7k~`z-7jgy*n^7A zY^#{hRm>wmth5CI+qttgmu`1GET&9U_yass3g}mD; z@m8?uM?Ge&DU%YvcgBMxJn!Eb2NF+02Mojvg_`m+6dvZK3gQd}F_a)uf*?5I22wxW zBCmJZTJ=04`sHueH(2E!Rk#3-_e;8&6G%AGrO2F}2_ zLkw!a$a9Bl7Xg%AAQowzi2h=zqEM&qD2pw-(U>dWuOi}@dtI4~|k4Pqwg@JFBp8UIcYYiStjwayh_Ky({D!WFZj z;)nyWaR}!yi?K0^qlVbrcoubqIYM!4OBEdTiQ^Ox2TkE@U7CO`D`r#0(h9_`xrJjF zWZ1f}1*;{Atd`9%7r0u=ZTV|Sq{eL^{3xOOG>{>5?^MhoVop}fjTQ6NAlA4!0ow(? zli)=*7Kzgxzjh$R68VBi+n&O)3pw~T9ye&&b_lEl9O0KjK8N1n*rzqXYEXTqsy|27Uqkg1JoU7IGhuVUmNi9+ zGBrX)uL8|M+;UCvUIn^NI8mI}NU^1>^TuKsHm_A|MJZyMS3s=IvjV2ki7DX)Bb`a$ zfr+VQoQ+k>I8-W8{;0#PPy{OtQzV~Zku#?#l4^>i1Cb;Ik#HKeVPvsBlB&$sTGgto zk!ocQKvg!DTBq9MAFbmDu{Pi0j^FQ_ZD5OUq%yNLOJ(NNN8X=z*bleUKis$Scb(6iyTueFy}w$T8}o#>6&5u_Y?D8$ql&1+GQZ z1|BwmfB?B^!!XH&A+rVPYduC`%W z=6K{i1W38EY$5S54w5|OK1rTR6^D2s=mTbZOxQDMivN!ng8E>f z@IQwVqwZys+Vu_zYtm-#Gy?qkN0RYkWMC8x&7&FU6N&8jac)yL_p?xgigAYeslA@t+lFozO`z7M^qS3q7pC{+2g-P#}8tCJ;NQp-!})q_VT>#8_Dy* zw}NV8?gexFF9?FDd*B674U1d{7a?pL5Zhgf?P$ezF|l3dVWV%>hye=u>d!<}Df(8> zSX#~hR?sjk4Ek2ket0jiJakrfO3C*>dMoIy;WCgJYUE2%@0btjKrTiLCO|HTH8amO zvLq0=HiNj`wZV4nHk|)dS_Wbr>uL47HWqAeZLnQi z!Ir%_;xxE6oGhFu)6Rryxb^|@qtSub9#U-QE4ER@mU*LV5n}V+5I9;xB1E}|Rqm&& zWlYT|*OX?f%3DE~s{Ym3K$wqcKg+eBFF~w%Y8(GsK`$UW%B>BvfYY}&AYoI~-BrycLv2t>f8-`+`~K{Tsl=`zwU{oI=y4< znI)TWyu>$MnRYi}mQ>|~lC97Y(Ep{gz|jab! zrCA$Qzgct{t*Cyps$Y7AsQ(1SvZlJ~N0iYJ@$y384`5fNG^>LM50+-X!pBjU|AkP^ zydp7R%pV18TNGPu#nzSB`gquA8cmwGEyOhY02>2tJEGF;SVi)(TB$CPe56Qj7$R1B z4#aw8ifbjuNl(&mM_QUKM5h>kTN$h|9~G*(%^tsM?MkhEJgt7;3<101(yaUzJQZ#a zmSRIMXb z>s=t$Ba__N@O$Y=u)Tb4FC!IHStW3OFrPmsoG2a|tk@n?4-F)?U5f3sfnt%rK&-k! zY+g!#P~}dd+!rc0SLI%%oWy%os;u1-4#rj#I_8(!PcQ9fI{P_KJ;Ex>@yO+vH|28s znZOyy?G-&38OoBN+=G!{ImE~ZBiC?2L6Uep7c_8Ar;kDOWf#OCcf7Tk_YU*H$Oo58 zXv~&4FdA_9-|Fx$K?^3x4Ioxg4tfqg7@0!2%%j2~h*iVG5u#c<$sY?gL)8DKif#v#Q~86D;}ACp+Hwy@eup8VCjYwt3DfEZi4)@^ zluG9JiuoAD+?|;FXWOd}+%_iPGUqf3d@N zAlCKe!=Zz^As)mI2jLv%^OV=nJJ&OHLrtpxP1Sc$^%+z@Ez2H~_gF8Lo?$p7Dh}=r zZ5+Zm%;zZ=D~?Y@sWA<8oB6xqSk+I=@HL3_)3q)Re>nUFwuFOB)k^=U7M_zyp7T9R zse!m-IEbL{S;{^bB5F9y1|--nLws*+LV;@jp_u>dE1267bG(B&9NQ10bHd?7wjB?z z2iBlL3v#*XZ7ygZ8DF~s*Z z;`g8!Hmj@pMyh@Q)ep~f-QpZqUdo7JcubM-NXWB6$Yqre>6)tgu&N(J^)*!e-F?Ku z?}Au6GFCxB z10GK3JE$Yp9)E8gKZsT2i8(Ho>Qf@5>gNa+`oe$~XpEjsWhbh#t5n%-RCfQwaxN~S z{pn(P{$p#cYW10=TCY^Ct5C3+4OQ#zL>c1A)Y{b3>UVK#u*Jm|g6ja%DXZ-Ys?Xkt z9J<)|{6{!ZT>K#rz#_*;_A-tqwnmCAQL)_!V$GT0T101=bD(e*<}KXzDK$I_pKw}YRrK+?7h%ZY zgkyalbp=htVBwAC5{$-cD9O#U)f#3mvBnP|*59cBWgqT2U&|K;Y~XzhbIE=8ZxYAm z1OFA|B$Q<8*x)zu2+rAVdT+%24o`3ZoACVt`sev9;Y10iTYwJ(m?$w|)ZzeI=m368 zkO8#f0J_)%2xNwF5vJ~do4C$@${{$DUZ$UxRrc4N;lwn`_c8nk&A_xw;>oxcg}&KV zF{LP`dqJ$n#(73{%IfF9^X6XVJHNLv&fwg8Q8-bIb(I2pQjN6@%c0p$fxXpJj8%ic zVtl}4+b7@J`HGI_Aqurup*vOR9T4kH#HsCi_v3en?Rejm`%xmwNM;WgXb(fQhdJz_ zqxvXDt?zs>PqwN(pF$U^P@D=~Nuj}1Ykz%Om~-C_%d-)Q25 zX=eX?8nqWd^^wz5;m3~l^`FE0uSNfXxLsu{)bE-K8?C6QuPUmkiaJwplis%_5XvZ7 zh_J%cJ(Mp%>uL-_#4 z6E$(&3cUdKmWFb30vn*fswuDz1eV|f=6%`I=`stAQY??=(Ri8`sGe^1V#ZvYGL=o5$UbDEju$Puu<@Xj3VN?t+Z*Os%aH3?&R*EfFV##1V zYF?w*mUI=5{IV+U58A8Nt)jwcK&@j`>!TOT(071X`;y(E``z*r*y0x1AJ}eL4b2hX z25>AaO1F6XgWsVj>a4#Ds$r2E;2mVx$*2X*v5M^&#nzqJ`g_>umNjAk$+nqT^7YyO z1X|5GT4W*~fyF@+@d1QZAmMgp2>x%s5oAjj8AyQ|c_p@FW`+)=J6f<*Oa-xShG`?F zUN&*N`vTjwRn6vCsn%abh0%dpGga$b7m3yy)EeVy^}DtaF1&a*|w&5Sx@I@|^h?4T;f|%T~G4&N8N!lslhh ztIDZ#Kh=K<`^nLMR%t(9gIM#`HYif;@8Cn7BKpr#x!qVO&0Lk6qH<4CZaG8J`xcQ~ z5K-lplA#!zZz-wZ&okV{mC5(#87?52Lis}1=7=PxEa^gxo!AzRJ=@1`LjIZL^XWhG z#1*t=*hnJi847wsCo$UZAl5C;t>N~GvR0K?^RE%)iB zsVsKNn6tpEO4N6T4TDjCu5hA6{U)e~EmlaV7$2hcGjCR4ofO!$Al41TY+Hn5ufPm- z{5*th1Jxs9qnkN^uyHH9nXTRY*g?j39&detx7vR@p%wzNTm*gyJ-!H>v3~@nd1Q@z ztd=dyaDtBbn#A~ms?d7=ux{kaS12FZ#}tu!5NC6Vt581r4(=*Sf;&aHCDz59n}r3d z$+l&}?LT79#Z6~%=tUxG$DIEo?HYN?In0l=_k&?z`5GD$)37%&?^2T$UnnN~6~wA} zHPmbI;|%5pB0?bQhu8Jv>^6>V9D@CEc6^?KpJUe#uOH&`6t6)IaEuZ^89$*kHSbm& z9TZ0faZJ0)#ZfGFikH)Y01t8u;WYPU;WC*A>!ADiX{$2S6qGgXMY9?eq{^%M5%1v;&S5^5YpFQi5~ap);&@1L zJlIxDu?@u9b49=u&Z6-h*y6rK#lkAocAs$UM{{r=Ti`wtaol%Xq9CFfUci-?kCVjfuInhuO)*Eojc*WE@Ihl|INravOmg^s(I`lG|SB z1){%YEi+CeqGgKcYDIKAi1ok_*NXm0uncUm;yA^U2~u4nOE~t&nqWn?z=|Yd#g$M* zE6Q!YkE$;eeiSRVSIpNb=27v${Fq{Xw6$1q2Z*(Aa3p5;u-P7i@8oNkJ%0A}xIKRD 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

ut!BySOs z;hz8w$4+l45{qq4N!~L2hA@%#lDy^kO$!F-2*-Z+7uC&MZMTDS7VD70c?TR0Mo?Er z01icW_E}PN>&_q$_$496Y33pQK*h zE=x%2nQ%q#9tok*DU;&5bcq|2JRv0sze7Ngv}n;1|BX)0Np5*bzy5s_T24$IIVLkJ zH9ezy<>VbI_tYV=E-Ui_DvjP-)4IapenQnF^)~jo|p>fO-xB2n==jp;g`47h{s#o zU5gV!AsY`vWHij0l`%DCv>#5dZhZzNMEJjVzf1ZgXc$EEU#rRn(#kfht$$QTTIR$Q zgrHxy1aa`bN^v^(2F86zyJ-aY{aUs7Q@JYJyNwRuP~%h^YaF|VZ~`|cH7zA0H|IRt z4pkqYDn9)2sa%!E*H(d59hV9mJ}#B3GOl)ttIGGcKiqL30me0pYi$Se;g83aSI0|Y zKCiOrQqjDRYwl`~n&&H*uf%aqf~>OrTKSU1knXZ-(4yJ7BP-ER5v0n;kvq~^vMQE~ z#2KkKm;w(I7pDB-^xwuCwVh>=XI(&tPGRX8>CNp`DrD2R*6wfU8Ht+p0gH(g!=Uml&@_e{R} z+q3>suH9Md%gM74@Zr_MARES+$WqPiCp~3~yZA@)lhQM!Ov=K771=-Kk|w9-jDv?J zCSx_#8v^|%IYzaG{r!*6P0NI1?JCT@!IclRR{+G72>w!ue0iX@SS9zfK$JTwX&2A& z2Dd#eIctJDUdLLt5+AAL{wfc!GWp5|7`%T>a_U6KQ$A3>Un{!5%HYsXh4Phw^WSer zQBq~`=x@gqO&^sKIrU@|uKvLN<>S~Ha>e(1aa*oE5SLCb9zFE^hX8JO5kv!Z#Z5&+wC%( zLrkLz`_nurT_1Y(`R0!wPX&3_f#~Y97g09_**BDHCw52Jqj_E9^`hFzH9GgibDv9a z85dJlHUhtAHYh8b1)2xS7u=_wSXQP=9-CGZRAG|+aZLmo6M4)UN4&wxDWHqbvo<8bw{j!oL0^BXtgH{{{twE^CW79(qpWNW=!3h<%9evB?JFzW z0{Y1Qva$o9AA!c;VDu}{R-pWMypmA0>js9ZO^K;?^07zP=G!xk!N;@f&}yTsYyf;7 zsu$C%Uc*c4A2YdjUa0Fyoz839_)LmYz9;_Qg&2$>!~RG9F#Nv_{CLXS|JWy2>@~-i zm7Pysw|X%*SMOmQH5Q6-$@cm9|5da%0d?C)c0gBQb0NRk-(LMSSO03XZ$o<$#^L&)#ue3g1MzJ``-#Y7nE`nXGPakZeJtAJ{OwnI z`Wqr<+1||GKGJLNfc7l3UmVci-qk-8?emZ)D{G&E_LazwkNEqqexEzOMQCq_d|4c5 zf5B~EjrRA^o*2;I)z!ZZ?SGz9R`yFk{##eR6zyA)ZyUi5p8aB~^9I_7Ux)uD^6=b1 zdx6{D0qq^nEGzpN{dn^GYdG>l(OwUExhNn%)0Lls_NmC%qd-0Rt7<+N`@`)Qr=OevuM7FT~M+UIsb4gvM#N4xqPqTf%t zm6d%J7~f~^_&T8dL3|G40<0&VeDk9m{X^0I!ewP;djk4Db@fj{`?P*#W#0w-x5xG0 zBD7yRpseiOfc{OQKMpEaqx}!8Q)l?wkwa=UkXu5$hihi(6=h{L{1|x8&!R@|2?75{ zL(9teXGGm`@SX+Q4`ZP*?zv)ES=mSE&%LL{JFcyJqTLu-R(4{*7WG_Pj0V5vIK1ue z*HPmO*OoV-ee(p=9$Y|NUuVQPwp@z#bEcJ*-HA2OYoA-kY2S$U`?1EJ0_wFlaP{v7 z|L&r)ve^OsGhF>Ov8LC&Rcj1S{ylemjnV#p?Y#+n7S+{1KJz@8WRg4~LlPjY28rxh zSX^*xLI@BENlb!*OP_=!glI@Y76dm?QR(tl&{k2?8rQl@)wYNmwYAdPg<2Q-T3f8> zYd1^X>e80q_uPBVedd`wLEgS^KfmAq^ZCyQGIP(I?>*<9d(OFcX70?h`>TfzU5YU@ zcD^r7N>=4iBhi zDjmKIo~!Z9c~M+UoN8j?ew=^s^+Si$9dGdyJ?ty|PjLQeoF8h;{1f9Jfj-7gp?k?p z|Nr)Hwm{buEufh9!`1Wkf3wj44_8kiK-*twe8MJWdV7~Ptld)^y~0Qv7T>CEBbOsw zRtW&dlp1L3|A*dVEuPwT%+xhwy{FnF6x6&Bl%0H@#sS*o{0IL$)JbLaMJ?=EtTn(4 z6y~($QclE1sgt&EGb3MqSXgwYHfF=ckk*G%9c}bA5ZZF|&O$0v8&-~~&E<;@p%<&* zkLkIO`6#W?Ci4DyNbA3s4S9&mBKIJd&*1YPbD7oAmHl7*yT3p@B3_B&JY6o~eGTs$ zc;ChQO}yXA`vKnX;Qemi@8$h|-XG+>HPKR=$NN0q7xTV^_cgq4;C&bGH}QTe?+19l zgZI06znAy>d4G`i*3m4V_j$Z8=6wn8Yk1$l`!3#Z;{8_M5Ac2m?|1WlFYovB{vhwI zNi3iDdAu*?eF^Vtc;CSLowIBOTvS$eV&2qcEBiWn`|#MTa86-y!K^-2oHdOJ7F{5R z6uXY94V*mlUe>~ce={2}k6NsQ?cPtjkV6;np-{?i5nGOnNOR`o5TCP`{>&YD5n+;2_M;jn8G+`UGNORa zY#_p^qvq1_aOmY!spDi90=|u!Q|`tY*L{f27bA|73q7Kw>>|Q1hJ#S-sfyl1v?tH< zl+4=!OrGt%qCnX=K5n!Ua6U|weEewA#i3u|OP)788>S8=C4Cz^=TA^O`NZLqiR30_ zK$=6QO>WJ+ixBv4C9fS@j?=EY1bb@5~K4PwJCo% zf{XP?4MMR0XrL&ZU~ZU{9L}a`rhWxRKjz_f+B>j!`Lcr=3X!(3Q}vQLs{^FPudV zkVTyy*&fdNJUCpp8GloI@h46FVecTJBT|yc^CEu@+t;J$Jw#pZ9ftYM%jx)ZbVtVe;a{QSN1$%ThDZu_qb3vog&AY$_?J*8MOf&b-0C4n%JaV78Z!+Ctv5h>Nhd6G^`F-XBq2o;Lzb$n!=c;7hy{ z;O?8o><0>4$?$sk#U^a)E$3EnW!@DgfzCnHowrp#Yt zktMB27>&#Pr82!6q5B(|FJ>v#B5jbB`BFM{Y;NTD0A{|d3VD&?RKc&ZD0Zes?uOMe z|5Fu;Bc)V$HJ$wTgvdOq?blgk+mc8wvP$M_%FqkYBe#%-)4<^Ep1u@yr%%OkuJ=hQ zy&Iu4^#0BoQZ?QoRgIfN0zAvkQW0V~hsZ+Tn|=*I+5g1x7O%ukU5Gx)X@?!X`L;){ zZRLzaE4^~tQvsOsCbEK8VSDd^(aX7-&MmdQ+fmBR$)R(n+ukK8<>kqm6{MLBN`qq->Ns5FqKDy1T7BCEPCv8E1# zD!%%th*Kojbw4HsGJ~j0vB?!1+k{-T{{w{d3>@cWZE(MhLZk?Svo0D&E?`A+P+!)?qv%8~I*4rT zx|iXvGaj0S@@#tbC4C%@b5Wp?DzcQ!pJyw)<X$s<0=L3Ei^4 zsS0}|*T6@z?^cC3c$SH)mUll$YpG=qsRl%~y zUyB@@{jKa5p`2@ve-b9mel)F#%xjNNfwi)~txBFf{!G|D`@0cRHrF11GmMtKQGF;j?jCm_Ij;wDYonx4f@HlI%sAbs`j-rVVhDvRO?7U$Y zQN^DJ)b=hT{VeAzpkz-9lSJp2Wd50Bx8peFYWzum0fepDh3-#KIO-t&SdrVQQ|6_A z1;_4D4XDYBDC<{7I#HNv<+<6j9bz5VMQ%JdgEF5rZXFelRfQx>AhL_Y>0qBo6_0v? z-1T@ha1wleN)b+J_<~Fyyj&HMti0UpQymf=p`4suGLc$kMJ^(jD4j@Q<3<*e*Dp*Z zA)YldH~Zu;sW*{o*6EYNMy1aPJmor6pkjxjB<*qPch|iV$EoCeX%wL#+=OG5dLPGu z!j7?=K3LTuI|>8Vgdxd-(cn(no-RN{Jvf zyqBrD)WEd>DXncQrI9L_1bdY)^b82`WSCBU~sW3GKZBkQ_ zq+)MA@kZ#Y%t`tJCn@p+y`Cq}QCRWoUJ1 z+iGjd7vcFyFFW=tj3;Td+5OKDz6?#l>4IqrzuL;rrV4)U*mp7iEX@x7cZq)-wdUu} zF=SP$KmSqUztORy%D=H5LR%mFvmr|J-{?%%{H)*2G5zwZNP)GEee3&Lkh(vu^m7rQ z^j?CMwBAW4{|j&1f9YhphDy578J`Vp!rS&;I_ zTp{{Q%iApSwrte$3}Gv91Ao&1k+;g=z9{mxGtLkOfE!M+flcIn!{ELo^6qAwA?yHd z(Z?Dm@?J8yZ6a?cs&$5Ost1;I3obql`NisaMOJBz?7yafYxT zxHsO`fD0M7#^C0NJV{rEKF6x}w{dLAo!itif& z`Tke`paqE!kAS%~?nIF%zHbQR`_3OVPUI~$xRXSl_`V^K?@#NBjOCli2sSuz`c{iz7JPdzJD-@N4)KRS1%D)KkGFuOy^X50iL=l@Kum z3X!7U#^aV7+{c2G5HSP_kvaP{PC{g>!5tDkB}5E?LgWF)i7)IiINYR;AJGkgLS)kG zTAsKpJ+{*Av&Cg4L=1sK%YGLxugl<4M4p6*Ay9~{eM93! z-Zu>{UF1oKFhPaL?-(X#dCkBgfeMzhkUK!w$gAsK*77783de}Np9j6#5Xh^izM^sB)vFBd z1(7FSZ3yJmw=qt-kG`Ix+xJVseZX!}fMbT5d+7QUA|He4+vSBc{ zmiMa26R$P|^6J&UikG+0;C?Og#H$T~yn6G0YMjWs#NhUeJn?EKsCnuK43p+*6<^n) zOwu=HnWvKTs1!cdN+GYz|CF5eD@pdn&uKN(vK}1T{b%Qi({2?X-F}IlzHAf1z$Myg zuagwM;}Y$(UlXUj`x4(p%fVrM(UP%8OB1K<87D@*(KPxJJp(g@-IrL?w=zzg_CABV zNpO1@X9#0V=&n@iihOia5JD!iX=l2cnHj#&fqb>psVF0)(KZ)1# zQ-ix*a7aT6X9zohdx&wO=NzoO)gE|<=qX9Z5Gd(%|1@6D^9}B9kta#V5cUH10pmp8 z?FM&`$lJ$y8p3|U{Y=ZdQ17f)4eoxCCrQT;4g%Nta}AKBlZ|f+>h?V#I7vE&KuPCK z#z|BzF}MdsPf0q4KuPDWy;`0mfh!E|5s@cJ#}Fv#jQWMfi9Gtv0I>K0rXXtOD!&6hChMn-AGRe?5}as$t>NoAFu$6G1X()_m~ zFvPMX`=O_FMQYV14(}rj(hYJ;zz)yh6ltl-qX5r z$#VA5(!_JbG7?>eK+$y_<2GtImS_v^T)|0n83ILD)@}_DuU%_!pBJx{=rRO~u4fo0 zQF4#LwTL{4E<>Q`n)5v^Pu%u(gIgsyi7rE+=(>k-qUUIsO?PCg=qb@<2ozmke@4p_ zJ!x5v#&wE3i7qB6PaF3Acr~vwur3WV(Phf;w9SkXCAFZn0Z%jK2KF>^0p)3vtrT*l z{Ksv0+DS?F*-vOK)cRo@+WquvQ2JW#6!A2uuR`urJ&F#Gd0{Gezo{XU6n-K znK-T0s}3~Cl+mE?Fe*NDY61;1WoZz}Rt=hArI5z?nIqAlJWR)TYAw`)Y1N?TkC6r) zEe%5ds|HPydCP##A+LfpHC!Ss`Ulq~y3N38-J;1sk1z!Ch+U6q9lox2)=Gmb6r6Yj z6O>0>vP<)e0k<32oPYsM83z0#qeMxq;M{-#O}T+Rf|{xfc$Aew7RrAN*N;j66}F2X z)LN(&={U6ed#8y3e;?+ndrX6r0sj!@i}{0k;i!PnB-_lKDt1W{N0!uW2$Z^SWeN_Vt0oYJK&kr+j1xUyG&oQ6l+d(mqGqXq!EfmL)JtPHwENc=i2=VT29)Xhm&AZFegASee)_%zSWHU9 zqM5ho$|Y2HL$dDiYs4}Vs)j(JdM)E5R6jDft%8$KH3SOP;R9Npcvo?u$lE3%Afajq z6skXDoP_FXgS$cGNvIkEh3fIQYIza^0|vKUa1yG9K%x2o<3!JW26vh(n&+Wiyfh^L(`o+gvEbHvklvNj8>xV0>fwT9~w#~NKM zhKPzGP*r^ER|dCM^prp~1PavO+@#yL zR`0Cgv!#9OMVd-=s1*jcL2weNhCqS(EaL`P-em@Nq39`rY6ujlS8~Hd z&z%OhQRGRWGC@z)7$$1IV_=&!%mk__(`1dW-NcwWP%qO8nlaUs8#quYq*S0zuu>=z z^KZ;Rpr(cGTQ=*~s`rp_h&4p>Bv2z^UIVoIVm+n~3-c4vofoSMZ5^lwBRpCA2iGNz z^$Y}Q{f3Jn_FSxI$%e4^Vr%-QE42<10G}9Kmf-d=&Jgwk=U%085~#E1io9&;u(ug! z2nT`N%Q(^VJcAn}@;+jmA#B3!^t!9#^}NmC#tLo=Uo{%SR^VP`oCNBB8r(S16OT01 z)q^1n0QWE(MD)x$R@ygF7_5ye17d1fi++}dnMczKf8NzMh%@I9i zKrjRv5Kj6^yq;;tOZ!d~c`_gv0u2aHF;4WHZ*V6GP6h-+paEgwHCmq7^Fo6=S@e_v z!4PObn6Opji(e=xZDf|CKk5NJRcwoL;h_Z@wL*riPL zlmWpIXh3+1agw0VGPu)3o(u?vKm$V2wOXF&d9A@M5u6MNhClc0z)siV%z$9Z7!Y1!lo&-P+*&Qj3<##&fZSi)Ft;9U#9Eo&^zlXj3QcArk zWaDozj^`HEhX(gai0_PvjSYcp{H4p|+4y6I^{|>v1{Mm~*pz9| z<;*5ZYC++EjZL|M2VF9e8gwUGDP-CFzF{!@k3#m>FVy1I$3t*v_gklk;hz`7%XQdK z#PD(*_R~=Ob(jZiYIv*k#6P)m$;OLM63gtBo{(rZ1d8U%F49WL^lp>E{X%dO&4xhH zjJF-tCJwmM;9ig(lxQ{tisl`R6L&voaK93H63vD{(LC`IEl;9y>dDf+{}h~fnjw&< z-M~1}v%%nA6+Ok%3_*F?rCOfod4s{dA@ao2n4moE?+g<)e`;U{G|WV^DZ|sc+4`cS z_Ov&(f+m_xxq&^6TtIo+ID9Ms$N8^Nh~FEsf7q*)SD&%Lq20gYbK+_Di>F~w(6>xt zp5|LE(It*Gu20t`j&&;pY5jJHA;hr^fgI}=#))J7%HX~wIB_gPAjg`(rW0EXKUL&C zEFC6}WeDV0KVzJ9ScSnoCi29w41pZ$a~J6LNsnD-aJvL2j%5hsSl?#cfQDO-8QkNd zr#O}&kYn|(*8tJ;puz1HdE!`1P>%H`!$i%==uq7a-_tPTSf&ieI(vf_CdSl=X9A98 z$_?H~hW6^M01~dUL!OmFI_JMQ0!?ZP*@w>8_0enMwAuaB=1Y@Wq)G63 z)udI@B<{$0V8yzIbX!5ESGTLZCAvKgLE5@)!HzTp>c~-RG)_$Mp}};UTQDQgW>ZF+_cBU^X~fJxn@t&QR!M|ZR*^HxN}-nK z)7Lzb?2vuTxmpYLRX`lt{ZA|sLxjZ;GQ~&=OfmG_)e}Qp#C2VvtF)39N{dp&;bn?p z2sFj`C*x#_ak9a=f|DtRAvoqzrkgSJfsHw zJXi=@fqRK@GIk#@I8WFl5f}m`f+Y>{dX9sIb-xW4JtYws0wsdkjq!Tc8Qdt5Cy9Uw z%CT-|n5g*$1IyJglL$;1jx~B^ypmeM(E-OYWi%;&;wbpgpKbedD>PJnc@l?q|MCj) zp}*LCC5oO$ zy)QWF8$+PJX+2BpF7dR};6AVeSFnaaeKY)QjT1fpVsIacJn0)IsJ_|AFj4au7)o3E zV+}KXW6J28cNry0YQ!g6L33SU$_?n787b(SpV;;b^;%E$)leMT{mv5Uo1aPF9OSekn$f&N2k*tZNu2Rwy`4tni}Xq_YfxIxBRB zmM3;-F}Rn4on;8rSu@Uz*R$W?4v0MIEGDSVx{+a`<_iY)W}vf78J!hb98&8YOT>8vNDvyduOAU!Fa#iJ6x%O`#F6xSsVx)n8PRlXl= zGZR#sXVz&Iq|HAxupb24Y|3c!EsTy}&pbfIcv|d&D1Q05Sv`fG&sHw8;QefO@o^_lrD<8$+PDd5>|D*;@?m0l`V! z7y`vjTeX%adVbyD9uz&{RcgR91d5x{n0V7BdcJ6I-x7HeH%w4I^hJh=nrTp5H}7E$ zGfB>r;X~HxT9_!Q5sw6X$dns+W^G3q4F177}GF_kg9y1Q@{s$pJ zuR+~p^Rux{ocS)d`NH=jz2Z2KObwAq16!7{XrQUSXW*`JusmS@e{EGXx5_n=mDzjc;~Zh42_{mup0x1e_sI zz~x|+rA<6*mBDQloCKU9P{4hWapGIwFt}}^rv#iKP{19F0gW~ZfR_#Kt0GSV&JZZz z?qZziISihs+xIoWNx&Hb1>87{FtmxD8^skeOAOasy)PX7DGSW80^EP9ZJzEp!~>MK6p1I<`J9Nn2(WQAygk0Pmy< z8UnM5^MOqb=Si@AnJe$mGOQ&S$+T`wVm#?AL!i$3gmE%-U14y|f|JfN1nR6cC0dXS zUXL4Gix^!x%MhruzFewtqUXB?*C+C%vzVYd>l22Fnp4qw-Mn>y&N5|m)*9S&piPw2 zhzkOpWy%el9m%vRJC3nZsEhI^W}yR4v+ajZR@GVRYxg*``8R7{d2KSOl`IDv1;-rQ^PI@8Zq|s9`71oVDMR4MzhCojGXU0jh8w~E$ppzN`Iq9`0#_M^5 z!Ig?UaZ)CzzOhcyFj4cT1~xy?H>Qlf>0y*8sT)=n=o?dRU?(NhDksgr^A8;7znh5; zn`qlb$7wCpPdebx?oY*(T=&l;>7TndYOl?gF`b>1KXD@+b_3TXEgFCzt>5&ZA()^H zK~L{#6W@N>z-9ytVahPX<^W0yDij3YRWs!Vw7F>%+MH$EBj+l4)@XAL@GRP_o0ua` ziY`-5Izk5~G*x%iUIHhc>PnNp!c~dx*W$F+WQ=ISx6Dl$y8oR~@trs}(21rD-PIi? zlC3&%n3X~r=TCLeps;P{&Cw0|Q18%*1-L@e4RUm!g|;*szLN>Qj0X zP3b+{N}+0Io)5z(y=CXJkljiI3HhZF^7bV95(~8UY1tsP&ion9CB15Q7i;8Qrh=Rm zqUCFG73lkkC8mPHBvkOMJ-0|BPc_ZF(p2!An!k#jbi4Anf*A)HD6T$9eaY!1b^8QL zPg3(ZDXvD7*OKkOYkyb}6!@VL_*k64WZX5u8cz}UaG?gq3w%0WpcElc8J_A;hL2b& zB&YDBQPAQ(`wi6fJZmyT*9+rUg1boh{C%?vU=TE2clZOW*lMeI)G$4E7N{Tn0n+Zj;B3p9rCfF6RQg62re?G7Q=?YWrm6aq zUnNCu7}(3EK96RGx9!}h>%M~P){((?&0OsoGj`8J-PqRaouzyHn)nWoBK7!XlwrH< z3|8;wjI;Y+HtKba*Xk|T>a88G)vM9!^hi@x2)qRpo+hz6x_^nL`q#5ksMeVasDB&m zH>PW0yn{(bpCf(SFpEB5qzrg31;M%Yv&=TZXt;>DGswK>+H_3Zu1mO?^erV&Z)eQW z26K@QV^yjwb}iV@S)YcIqTTDvGOZZ zHRp8(u@ZOWCw*0O+yrWBct3weaQeHW>-R|EC4T{v~0QIL7mD`n}OYrr^^O!qk z+^2|}+zw~ySi2pC$jk$1FsO?e6LY(FeHa}~tF1YwgBxFhrSAQqxvFEhD!c!=K>}A>#~q8)_Aivm^5uR`Nr>-RU0paiR?;>#&;DhG z_Rxlc?v_=S{i3?hOg6Rb;r21ZveU!E#ts{9XQg<^8likEJ^fUWU}rvb;TXqS73gPY zq!k`^V){wx^U|r9oC#ih#dNYd=~9WvsUz$uqiH^o=BN|t*>-qXdb-9DJ(A(8<^vY`Q)ZHVYC2UI!+BG|HV*v5)d3tNixqOTLfJ+V2vkW9OH$`a(g-XDrrtkzj1)NVjhx)FNntMbpqmTg2Lk9 zs0`FbEP2^+(kBufpD6{^SMj8|zIsB)i}{$(Qb(W(Q$|zQmE>MOU)lT^N3*IH6YFHg z8p4#p*c&u!#a(y71?t~Q$^F$;VxMzWY!t_5b?-$!65>A6mVV2RR~__XRXMN zQ@V(iHq3ZMNeoB+V3-lHU^Os_LcX zMX`EG3;JoT07YAPUH-THls+ZA~qT z*o5!UT4dp-&W=@WtzbfQwf1i4YU$CHwXW^Mk9za5`oUm6CX?Xz*}2ry-qs_>YZ}`+ zm`oMn5YgVcuCcvOj@PvG^fa~_5nYYljiev3X$x?nt+S=0Zw+Yz2c>4l8iuS&4Kocz z+o()Dz1@u+J*!%}0ZY*0)zo6kv8~jQ%88xqViZaWZKYp4Awu%I_>)4#RCjsu^2?LL;a^)@HEZv#07@X<3e)9IDPPaZ=rLowU%DkO_*GYZS35RCn zs4Cnnqa7Wdq7@D04^ zcIDm^I!%cl|E80Ar8Cjp=A`X%+{;P-B3*$prmx-{t%{PNd*Oc5B)^35$>FtHf~O=X z<2dM-V>T58PR%$xhcjAbc zG+Lw0b#E zk7HlQqub|Z|KD=29QRx;{(s58TC{(Si}&{M`$yvHiPF&#gf1-Gt))`j{!1O-=bpkH z5c-VHKVr^A4$!I{V2sZJ66QA~GAD&X#2J5@GZr4aYqP4(pD0wG{)~LuL-}-2MvE8e zj744v@46y^1Nw%7`zb-LH`VDeU~qbc9LLl>)Tk14`phAneh9ZgT7GP!_M1J{5zTuBPwCvNb&L(w1xMa- z3FcH>_mLWaqnbkrALZ*VzQv#}^N;L0sqb-TM0hRTM+(!;5}DsP>zonpna-GXj^m!~ zjO%qA+&8(*315(CzLOw6t~mTSpl<*i3C~-DnG{&t0h8#H^~_VvU=!aVpqq#A$Gl#3 z0dzoL0L3L`)kkdE*nNOl-T$pn@&^@sSPdg1_P+rG7on%#FWVyF;3@47shiMAF5NtKkosVY=1__KJ7>i2 z$QVZ-h)w?gr{1P-aE7#3;Uf41|ILyq{jFjM-Ggbepq zSGM}5;k&vCgKvq>Qcbu3_hYCDq1hUKgpK$&u?HeGIaHFcQMWl0-g7eFcP2c58=+Zm ztvJ%{EXNK_!K0Qip8L2zF2SP_)HD2jJi(cWheZ>eQLi{hVVC=gle13|!tNwIAqs`@ z#C6dEJiqMj+)&lo+qPia9*20>KXnlEIeYCD_X|%4sw7xt#I}ve?)!VkFrL(U$iYIhU=L6Z) z+1z3+s>egZh3$>4Jyuj5m$f(c^r$D5Q9QSdcC@VTjb6~!yE@w3(%!hCr5PGkR7cBe zYpZLc3rj01%NIl!msTyn9?uA&(wx~=b6ZbWXAkOT-OHEPR92K#)JLnT>nj$nuoiZ; zEl>|Zp>jP;5XFTvaMJwYM~O^mRp7HMX?_dj>uFv=&v+6YGwqmWqzH-i3V~O|d4P*SDrC+S>`c zk`5PicXqVGfYAlzXH=Axb4wSMSC!YIJJv;4wRbl5#=vZq=o&nyg-&a_+FN>Cnxh?^ z(51Jr8^`|UoLRlBazV7Vylh!*9lBEb5RZT12z^bOTX%R(ZN(Yr%t4wgD6KCgLq-=? zFRNN$MT5q)&Q~3%J5w4R)3>AL0^LO2+#bJn6;)@HR#q&Cmewv>wzRye-da>c&xW;k z#4J+3qNY5$4Ce7;tE!`QHKk?c+M)bsYRgL(M60VRSHzSMqcu^7v`5v0Z9k-@wtD#r z)yUGy%IY)WT0W<%uUHD7t6o-b^{npf?#+YG(Zgr>Cp}kJ9-v&IwFRyY=ZdbWu6_zp@0pvhrxYtanUB(1 zrC`VCz-m=GS*VN{tyb+$x^Anrr|rTPYgOae4&ALJ!|R_b_=K}s~bC-+grM|Us3CQCk5+QfK)E4uBs|8tB>#C#ns4W%c|fv zWs6nHi**6IRN0qR@K;w_NshKcTLBezz(UBX(e}=!#@@Eh4y$E-7gl#5HW42xIC}?s zg$gdydaJdu3;orC^nkFz5(=NGbP&|dhYC7gNjfFza?7{^#hU7<&I>wV7;;ACsaU!a z?OEN}4PCf@#a9zLGA-_ixnUsD#HHqSQOdx_&9x8<=q5_!zQBnk4V9?DHn`{6`D5ZJ z|65oHv`(qtlTg8~?#`7hR#RhFZ(nzd)sFm`r~dSH^t82hv^1*_)$X{kvUCxKje4X~ zvP`t2aZQWWs)vo1j_yu)wMm8;7CO6Hx_jGNe9j+8*8YJBsZb9qI?{YCuPLuxT2WV5 zQC)?ouBw1{^dMv#oA3ueI*uVnL~Tf5lCsho$_q&S+P20U<5ac_(8>MKesqZL&(NQ6cu zNvvwr_K!iT$76i(Oe-4gm>pN8O1gSTM}F$*YUzj@yEsV5cPbkVY&mAe0+m5wQz&Qz2|t16I!2K(w6<+U`MSl!l4y=ZK} zd0NE@O#v_ht4`7J=hIo`nOIQx&YW3WU9~7$USArQF=cq9>{?a2wA{jSrzqw+O`TYO zW+o|RGM1~c)2Ft|7-v+})-Qwe2enm03FdZ#f|iHEatwK;%jy>=j6ah-Vm4Y-4%ezI zt*eVkiccKI#+9YZqiWcPsjI6jDAL>36m7)-w5qM6MH!9E+Xa7UQqoO&Q<=E0gT^|a z?MfFcs4cIngJ(vU(HsY>+a&v|k)xYtEEpPM22**eshwtwv4q>$+qtH#3B!nX37Vx^ z9o?Ir?-(J(~Q^4knQUF zR#Lj=iSf$zzV4M)S99N*H5({yW+m(f)pwL6TDm*1YwPuOzMe0u;f37@K}|{0^aH}H zuD*6zS-qYsnPdir*f_qlv~Gzn!3L57`EuQ|nwsib8d2%OfakC@S;Lw6$_49cDgtx2 z`s(WF($cCG(dwG=Dm8hi^Y`ciOv3dvSm(e&&4Q2CSH~QUhP{r?D9;CCbV&#Cju_RG zKr2eiz1x~wI@Gn$e`Dci5%oVWMdVeLh=V$0*Qjc54Y?yiOU(s!e8u71YGM1Q=y$*1L_b zUU*(55j8lx=)#8sY}KG?DOG5qMY=<`P0hsPd`-EmKP}hjt2Ap-)v~;@dGqpS6&KDa zEY8E?-@cBPQ(9Xv$7yRqv9Y^p^(k0{TX5Xmyn@!ef}Y;yQ<}hEu&Sn_41XJYHgq(t z#${G#Ur#|_#|3R2SjkHv*n^XZ?56VzF!J|evewxRFkO$C6V!jJIMKAqS2lgK zpd9^9Q`iNu7R$s{Iw^KWe05bzBh6uYRPY3@s*4j5XTfh9o15D@kT6wtSlCquhgRuI zU!KHGf{gK1zK`|*hs>iUOFTcIOJRTT&9ALqf}pIas6j48&g#ZV<<@E@g~YwAJX$&* znWMDKHxG!Ln#5gGr~^aTO3&H}nRi7r||7ZQugXhbmA>Fc6m8qAn63oB+gR4nBdZ^%ZiFBjbw)Q+Jee|3 z;z#q;l%S3V(pX)3WS~4i+1H}6oaen8Voe=1pNltwICT7d2iSyeDj~q7@FrI&7HKOoN9@s)Vedt9itfJ;_YV!lFEvzC3O~N?;Ww}d}sIIY$cO{dbx;< z%UN;>C$~vjr6-Mj$!xXdiGcMRR#aZ#V8{o=&eW_`FYsTFgGG^ld{^`O*&M zYm$y#x$~gn*Hr4uPO&#&^yPlR%D%StW_fpJ-u&~ATUp(8l>2Rd6B`!tr*yQYitf88?>ggdQ6U6Djr zZlGIoF}>7)>z7N0s#~#iel_lNMJZodxQe5EX_~?pl6t6)yU$`+61O(-Eo5~on{IW) zBxtk1CDomE+>oFfRZ<+(-;~G}S5sh+Ppa`>-$|zHwncTFeceqh>drRausO^|uX!a| ze?1gr!5gZ0VQHAPdvg34yiBR+2LtqpOfTj)KLvF>4s2_TwVU7QC3&=!;lFL=SUYD5 zMUk!Zup_x$fqK>{{%mVUfd6ZtC({3$T#vk0B=41em+8SbRG(vd@KxCtnI3%U_HCvI zU*8R(Ii!E^Rp1GrlO6W39q`VS+CInhy-cSUqG+R6|EZqf3&>Y8{jI-izDM-Vx`XSH zH>aikrA#cd!S4mU!t~&`3*H9ZwmeI|LLe_yzt8-^Pce9S zjg9yZ@>dJwmGW_*lO2K|i6~@x@Y50}F+KRrij_e6eC|r@a|iPWzlHWR^UL?l#_H;==b1nFUBcfn ze+hq13-7zD?H%S1ejwAqyQE~#h5-L?(8>P6kBH7?{!Pq3iR&+6{@@p4o0)$%^XD^v z7xM?d_WNb#mrwXg9BpU*;1{O9&HVD&XuPnjw(l{2@YCGCVt)DFw)pd#%pd#=c`9Cb zRrcY}Qj7j0Kqvd)1y;4mx8hG^{sHDcMxVA8GJo(h`)4!%UgpO~W7O8l{J~!u*uwns z^8pi?|EtU&{DFq=Fu(lB!qLqC4D$zn6l5Rs%P)P1{~uufK|fcMqU|F;IV1MZ0G;gf z=q4>tepjf7`FBV303!OF#QeeEpgW8C4>JEztp6(J5B|91mCP@{7%BYQm_PWlr{86M z`RUThT>tl(KlnSd?=b%+{zb|e%>N1V2Y>K*0tQ_2pWVza{xcnPvQO}r%&VDSex6+X z=WON={&f6B%rCzOpU3)K#r$VKp$YXEw`hBq`5XA>&t?AeB=bM?l;&H4{-*5!^A9lp z6iv4N!u+57Koc(K`ZH2>d-pQ`1m@2Lo!WcxbDA)l2dYBmKgj&zZ}XV{L7He`8w+`~ zEn|K!Q~QtPhX&^V(XTb%B$R2pg856BU-H#gnEy|2YQ7y@|F@WbQ-J>o=I?(;^E+&x zmzjSD^GiN|o%wHlU-Li3{C{Kqz05D|4bgX8V4vuxntu_`uf{R`uD@$Ke@6^Hm3V%8 zEck6J%c>d66=<0Hn`k~BA<2YO$E4MA@hf_@$7iS!>({KE!)mFejr>iNYG^!J9KPr!v= zqIS(w^kG);>rH2Xo=DFPL)3Ha5cFL`&|e>d{_YU;>Bu*U^f_?|`XbO1wRg=B^y`P9 z|6qvvUjsdn|NLnP{$xxn6X`zz^hA0t9D;uS5cPav2>LBU)bpbu`1cLLk54Bh)H4V4 zMC~dVf`9Q4^o}9uUl^kPhlk+*?GW^AD4EF4bBCbM2R%`{&H*t|zpopDe>3QbH7jJxr#GRX;1H22SYeV`q!w&-xZn7@j%v;(644W_b(} z<2oV6WtJSo&K^JbtXh`?i^p`GbDWQhmNENI9UC|$PqAYn=EgYad4mt@>xn^+G-LSL z@%Z?)JTbO7dt#NkWGJ6JpHbr;A;ehlJLsmn;#lGy92udrea4Mvk9$B63S-vhz%pdT~S-x%*K)g4_TAiQ|<5?4XN`j6Jzyc2% z3lsD(=WhMdf$3de=lY-f#Z>lnX@V7|rXF8+`s;~*VZdyopwT zr=hKP?>Y#u)mEwaXA#0T86HR!PY4!Q)1*(!o zXk%G)cH!Y?o@hdBePy(<`MkctuXQErO}v!RZ7inL(?dx@-NdqL>Oh{-l8_C~)XCnV zyIydCS(~bX*8vYz5#DG)Ynt_B%{YaB3QTivly?Ln%3QmKuu_JwdhD{SlvRMt*%d+XhtHo(lt<0T9Z%%;iKl$WIQ1g z3y}y@Q&n8n^t92XB)JUc_EG&_j>NZ7fGTe4<5fOG)UKnZu}i(GAR5KIgzXXTWLw~l;Hr1G33{h-9d#QNb)A*Q4Jldw{HF@Y9UDj$ZmI5|>eT+q@8@8g$I zRZ5la4kT20{S=@?eu+_{X?-J&ENX$5$vd+Pk2R)_OQ{JJ^_faLHNCW^l75@2HYzK8 zgJ|7bv7tjQsqyXSo(*fTJQ@4mZoOYEN0?}}6ygiRg=WQOp&{utUU+jtF|5Y2Wvj4x zLkB9;d#rX>XXx9-^m>}VfbXmqc4JWx0r;q^z1J#KSMh~7ENtyWkq)qA5FdW0tE@u0 z{{rq#wOF;VWi`euy8p7e8MR4C*P(B{XoMUSiiDy9rC=kyenE0b3@EfRw-9eYT7_`G zH5frbYgyTc41*8;(#sfJZ0lG>73fndS9aqkE4YLYGPlSvEGW05{-=9-ZjMKEc($Oo zyZJ!&c}$lbKMs*t^%{fS1~1^7?4k@|{ppO!Y+@6$yiU+~iY%W$6X z^O?`(o1U`Y&xWTRVS@NCfkrmr$9S=uxOpIAlXV%oq-Du5?XSXrKRz#ysHi4S?W`K^ zPhE=Lla+h3+i=2<@8SoUvKPLg@bnuPe*7lJ%f93@%7$$MIV=HG(ypAFy_XKA^z zkKnnke-r-^+Rt%7E-z-5^@=;j>3V|vQkMOzz>>=gUe-tMJVN|`0ZWnuFYEYrGd@qN z0S>+8$pP=_F*flM84Pa6V1;%Lp8-5{KLAH*L!1b*t}n)6gK3|AAxmkohm zMGIH4ZI*>YL*T0!A8h9zZqxYXlIR0?eOB8s5aNn+@#C5!mmjhn4eHhSFa0OgQg{cN gsPGLx)%YSjNAqtYS7>xP5&oLrXwI_&fI!juKPBA2K>z>% diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-test-driver-v1+1.94.0-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.0/libdevice-fake-tod-test-driver-v1+1.94.0-x86_64.so deleted file mode 100644 index 9afb57d94bfe3d1e3e9eb36abf71558bbf8c15a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42912 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 Date: Tue, 20 Feb 2024 08:22:42 +0100 Subject: [PATCH 78/79] build: Look for sh just once --- meson.build | 1 + tests/meson.build | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 9f1c8a62..3d3050ba 100644 --- a/meson.build +++ b/meson.build @@ -91,6 +91,7 @@ gusb_dep = dependency('gusb', version: '>= 0.2.0') mathlib_dep = cc.find_library('m', required: false) # The following dependencies are only used for tests +sh = find_program('sh', required: true) cairo_dep = dependency('cairo', required: false) # introspection scanning and Gio-2.0.gir diff --git a/tests/meson.build b/tests/meson.build index 39066e2b..b6c44ca5 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -151,7 +151,7 @@ if get_option('introspection') endif else test(vdtest, - find_program('sh'), + sh, args: ['-c', 'exit 77'] ) endif @@ -205,7 +205,7 @@ if get_option('introspection') endif else test(driver_test, - find_program('sh'), + sh, args: ['-c', 'exit 77'] ) endif @@ -224,13 +224,13 @@ if get_option('introspection') else warning('Skipping all driver tests as introspection bindings are missing') test('virtual-image', - find_program('sh'), + sh, args: ['-c', 'exit 77'] ) foreach driver_test: drivers_tests test(driver_test, - find_program('sh'), + sh, args: ['-c', 'exit 77'] ) endforeach @@ -273,7 +273,7 @@ foreach test_name: unit_tests # Create a dummy test that always skips instead warning('Test @0@ cannot be compiled due to missing dependencies'.format(test_name)) test(test_name, - find_program('sh'), + sh, suite: ['unit-tests'], args: ['-c', 'exit 77'], ) From 76a95ce45f32bb4ee33a5f3c23e34346e4f3682e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 20 Feb 2024 08:24:12 +0100 Subject: [PATCH 79/79] build/tests: Skip a test if the test requires it during inspection In case we don't have dependencies, we should skip the test, otherwise we can just fail at test time --- tests/meson.build | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/meson.build b/tests/meson.build index b6c44ca5..db61c1b4 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -100,11 +100,17 @@ if get_option('introspection') base_args = files(vdtest + '.py') suite = ['virtual-driver'] - r = run_command(unittest_inspector, files(vdtest + '.py'), check: true) + r = run_command(unittest_inspector, files(vdtest + '.py'), check: false) unit_tests = r.stdout().strip().split('\n') if r.returncode() == 0 and unit_tests.length() > 0 suite += vdtest + elif r.returncode() == 77 + test(vdtest, + sh, + args: ['-c', 'exit 77'] + ) + continue else unit_tests = [vdtest] endif

R}&j-64$F- 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 KYedmL@c$2$yh$ej diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-test-driver-v1+1.94.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-test-driver-v1+1.94.3-x86_64.so index 3675ad1b0b4c0db706575621c483f7f59c5a0c02..6b548458cc905f9419c11f82f2d1e1cf2bb2ba23 100644 GIT binary patch literal 46744 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 eUqt^d`Bj3-SHW3`xK!k%v3mr?LJ6q=^Vi) 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 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-ssm-test-v1+1.94.7-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.7/libdevice-fake-tod-ssm-test-v1+1.94.7-x86_64.so index d78474ca4137fddded971cb73105cc5918e35e9d..e05e957723a0ea1cb7afce5243f90caddd098d26 100644 GIT binary patch literal 132072 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