From ddacf07e3b79a03639eebf746a3714660be15629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 13 Oct 2022 22:25:23 +0200 Subject: [PATCH 01/50] meson: Actually bump the version to 1.94.5 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e25a1736..7bcd9781 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfprint', [ 'c', 'cpp' ], - version: '1.94.4', + version: '1.94.5', license: 'LGPLv2.1+', default_options: [ 'buildtype=debugoptimized', From 36bcb24b3a3b520dead248ed493c7b102500141f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 13 Oct 2022 19:42:50 +0200 Subject: [PATCH 02/50] fp-device: Move FpDevice private functions to public library This these functions are not really needed by anything else than FpDevice, so move them back to the cpp file, so that we don't expose them in the private library, given that we don't need them --- libfprint/fp-device-private.h | 3 --- libfprint/fp-device.c | 26 ++++++++++++++++++++++++++ libfprint/fpi-device.c | 27 --------------------------- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/libfprint/fp-device-private.h b/libfprint/fp-device-private.h index 9b2ea27c..759a678f 100644 --- a/libfprint/fp-device-private.h +++ b/libfprint/fp-device-private.h @@ -111,8 +111,6 @@ typedef struct GDestroyNotify enroll_progress_destroy; } FpEnrollData; -void enroll_data_free (FpEnrollData *enroll_data); - typedef struct { FpPrint *enrolled_print; /* verify */ @@ -128,7 +126,6 @@ typedef struct GDestroyNotify match_destroy; } FpMatchData; -void match_data_free (FpMatchData *match_data); void fpi_device_suspend (FpDevice *device); void fpi_device_resume (FpDevice *device); diff --git a/libfprint/fp-device.c b/libfprint/fp-device.c index 17178d19..c1436448 100644 --- a/libfprint/fp-device.c +++ b/libfprint/fp-device.c @@ -1088,6 +1088,15 @@ fp_device_resume_finish (FpDevice *device, return g_task_propagate_boolean (G_TASK (result), error); } +static void +enroll_data_free (FpEnrollData *data) +{ + if (data->enroll_progress_destroy) + data->enroll_progress_destroy (data->enroll_progress_data); + data->enroll_progress_data = NULL; + g_clear_object (&data->print); + g_free (data); +} /** * fp_device_enroll: @@ -1217,6 +1226,23 @@ fp_device_enroll_finish (FpDevice *device, return g_task_propagate_pointer (G_TASK (result), error); } +static void +match_data_free (FpMatchData *data) +{ + g_clear_object (&data->print); + g_clear_object (&data->match); + g_clear_error (&data->error); + + if (data->match_destroy) + data->match_destroy (data->match_data); + data->match_data = NULL; + + g_clear_object (&data->enrolled_print); + g_clear_pointer (&data->gallery, g_ptr_array_unref); + + g_free (data); +} + /** * fp_device_verify: * @device: a #FpDevice diff --git a/libfprint/fpi-device.c b/libfprint/fpi-device.c index ba750654..1b9fa8f3 100644 --- a/libfprint/fpi-device.c +++ b/libfprint/fpi-device.c @@ -522,33 +522,6 @@ fpi_device_get_driver_data (FpDevice *device) return priv->driver_data; } -void -enroll_data_free (FpEnrollData *data) -{ - if (data->enroll_progress_destroy) - data->enroll_progress_destroy (data->enroll_progress_data); - data->enroll_progress_data = NULL; - g_clear_object (&data->print); - g_free (data); -} - -void -match_data_free (FpMatchData *data) -{ - g_clear_object (&data->print); - g_clear_object (&data->match); - g_clear_error (&data->error); - - if (data->match_destroy) - data->match_destroy (data->match_data); - data->match_data = NULL; - - g_clear_object (&data->enrolled_print); - g_clear_pointer (&data->gallery, g_ptr_array_unref); - - g_free (data); -} - /** * fpi_device_get_enroll_data: * @device: The #FpDevice From 1f1ed80dbf0ca47b35a8eebb2ae67b3155f6b6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 13 Oct 2022 19:46:58 +0200 Subject: [PATCH 03/50] test-device-fake: Add more logging showing the current device action --- tests/test-device-fake.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test-device-fake.c b/tests/test-device-fake.c index 321bfe5b..558ea915 100644 --- a/tests/test-device-fake.c +++ b/tests/test-device-fake.c @@ -20,6 +20,7 @@ #define FP_COMPONENT "fake_test_dev" +#include "fpi-log.h" #include "test-device-fake.h" G_DEFINE_TYPE (FpiDeviceFake, fpi_device_fake, FP_TYPE_DEVICE) @@ -29,12 +30,28 @@ static const FpIdEntry driver_ids[] = { { .virtual_envvar = NULL } }; +static void + (debug_action) (FpDevice * device, + const gchar *func) +{ + g_autofree char *action_str = NULL; + + action_str = g_enum_to_string (FPI_TYPE_DEVICE_ACTION, + fpi_device_get_current_action (device)); + + fp_dbg ("%s: Device %s in action %s\n", + func, fp_device_get_name (device), action_str); +} + +#define debug_action(d) (debug_action) ((d), G_STRFUNC) + static void fpi_device_fake_probe (FpDevice *device) { FpDeviceClass *dev_class = FP_DEVICE_GET_CLASS (device); FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + debug_action (device); fake_dev->last_called_function = fpi_device_fake_probe; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_PROBE); @@ -55,6 +72,7 @@ fpi_device_fake_open (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + debug_action (device); fake_dev->last_called_function = fpi_device_fake_open; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_OPEN); @@ -72,6 +90,7 @@ fpi_device_fake_close (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + debug_action (device); fake_dev->last_called_function = fpi_device_fake_close; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_CLOSE); @@ -90,6 +109,7 @@ fpi_device_fake_enroll (FpDevice *device) FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); FpPrint *print = fake_dev->ret_print; + debug_action (device); fake_dev->last_called_function = fpi_device_fake_enroll; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_ENROLL); @@ -118,6 +138,7 @@ fpi_device_fake_verify (FpDevice *device) FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); FpPrint *print = fake_dev->ret_print; + debug_action (device); fake_dev->last_called_function = fpi_device_fake_verify; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_VERIFY); @@ -149,6 +170,7 @@ fpi_device_fake_identify (FpDevice *device) FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); FpPrint *match = fake_dev->ret_match; + debug_action (device); fake_dev->last_called_function = fpi_device_fake_identify; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_IDENTIFY); @@ -197,6 +219,7 @@ fpi_device_fake_capture (FpDevice *device) FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); gboolean wait_for_finger; + debug_action (device); fake_dev->last_called_function = fpi_device_fake_capture; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_CAPTURE); @@ -216,6 +239,7 @@ fpi_device_fake_list (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + debug_action (device); fake_dev->last_called_function = fpi_device_fake_list; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_LIST); @@ -233,6 +257,7 @@ fpi_device_fake_delete (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + debug_action (device); fake_dev->last_called_function = fpi_device_fake_delete; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_DELETE); @@ -251,6 +276,7 @@ fpi_device_fake_clear_storage (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + debug_action (device); fake_dev->last_called_function = fpi_device_fake_clear_storage; g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_CLEAR_STORAGE); @@ -268,6 +294,7 @@ fpi_device_fake_cancel (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + debug_action (device); fake_dev->last_called_function = fpi_device_fake_cancel; g_assert_cmpuint (fpi_device_get_current_action (device), !=, FPI_DEVICE_ACTION_NONE); } @@ -277,6 +304,7 @@ fpi_device_fake_suspend (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + debug_action (device); fake_dev->last_called_function = fpi_device_fake_suspend; fpi_device_suspend_complete (device, g_steal_pointer (&fake_dev->ret_suspend)); @@ -287,6 +315,7 @@ fpi_device_fake_resume (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + debug_action (device); fake_dev->last_called_function = fpi_device_fake_resume; fpi_device_resume_complete (device, g_steal_pointer (&fake_dev->ret_resume)); From 54bb0c12e60283ec67b1fcdcabb63d77c0d959af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 13 Oct 2022 12:03:31 +0200 Subject: [PATCH 04/50] fpi-image: Check for PIXMAN presency using #ifdef That's a defined variable that may be there or not, and currently we warn with: - fpi-image.c:29:5: warning: "HAVE_PIXMAN" is not defined, evaluates to 0 --- libfprint/fpi-image.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libfprint/fpi-image.c b/libfprint/fpi-image.c index 47aac8d7..b21982e4 100644 --- a/libfprint/fpi-image.c +++ b/libfprint/fpi-image.c @@ -25,7 +25,7 @@ #include -#if HAVE_PIXMAN +#ifdef HAVE_PIXMAN #include #endif @@ -107,7 +107,7 @@ fpi_mean_sq_diff_norm (const guint8 *buf1, return res / size; } -#if HAVE_PIXMAN +#ifdef HAVE_PIXMAN FpImage * fpi_image_resize (FpImage *orig_img, guint w_factor, From 6395dda01265d197489938e4cc770992881b1694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 17 Dec 2019 04:33:20 +0100 Subject: [PATCH 05/50] fp-image: Remove config.h inclusion in fpi-header And also avoid defining a function we expose depending on it's presency. --- libfprint/fp-image.c | 1 + libfprint/fpi-image.c | 9 +++++++-- libfprint/fpi-image.h | 3 --- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libfprint/fp-image.c b/libfprint/fp-image.c index f19c5dfd..8870cfab 100644 --- a/libfprint/fp-image.c +++ b/libfprint/fp-image.c @@ -24,6 +24,7 @@ #include "fpi-image.h" #include "fpi-log.h" +#include #include /** diff --git a/libfprint/fpi-image.c b/libfprint/fpi-image.c index b21982e4..98f412a3 100644 --- a/libfprint/fpi-image.c +++ b/libfprint/fpi-image.c @@ -24,6 +24,7 @@ #include "fpi-log.h" #include +#include #ifdef HAVE_PIXMAN #include @@ -107,12 +108,12 @@ fpi_mean_sq_diff_norm (const guint8 *buf1, return res / size; } -#ifdef HAVE_PIXMAN FpImage * fpi_image_resize (FpImage *orig_img, guint w_factor, guint h_factor) { +#ifdef HAVE_PIXMAN int new_width = orig_img->width * w_factor; int new_height = orig_img->height * h_factor; pixman_image_t *orig, *resized; @@ -145,5 +146,9 @@ fpi_image_resize (FpImage *orig_img, pixman_image_unref (resized); return newimg; -} +#else + fp_err ("Libfprint compiled without pixman support, impossible to resize"); + + return g_object_ref (orig_img); #endif +} diff --git a/libfprint/fpi-image.h b/libfprint/fpi-image.h index fcd62b8e..3554bb7b 100644 --- a/libfprint/fpi-image.h +++ b/libfprint/fpi-image.h @@ -20,7 +20,6 @@ #pragma once -#include #include "fp-image.h" /** @@ -77,8 +76,6 @@ gint fpi_mean_sq_diff_norm (const guint8 *buf1, const guint8 *buf2, gint size); -#if HAVE_PIXMAN FpImage *fpi_image_resize (FpImage *orig, guint w_factor, guint h_factor); -#endif From 89509c76f4a533217088607e1f0822510fe97859 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Wed, 2 Nov 2022 12:04:34 +0100 Subject: [PATCH 06/50] build: Print the list of enabled drivers This saves us from having to figure out which drivers were enabled during a build in some other way. --- meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meson.build b/meson.build index 7bcd9781..1badb164 100644 --- a/meson.build +++ b/meson.build @@ -321,3 +321,5 @@ pkgconfig.generate( subdirs: versioned_libname, filebase: versioned_libname, ) + +summary({'Drivers': drivers, }, section: 'Drivers') From a5d52eb853d79af9a79184942efd627ca441d134 Mon Sep 17 00:00:00 2001 From: Vasily Khoruzhick Date: Sun, 8 Jan 2023 23:36:18 -0800 Subject: [PATCH 07/50] Fix indentation issues with uncrustify-0.76.0 Apparently older version didn't find this indentation issues --- demo/gtk-libfprint-test.c | 6 ++-- libfprint/drivers/upeksonly.c | 30 +++++++++--------- libfprint/drivers/vfs101.c | 6 ++-- libfprint/drivers/vfs301_proto.c | 30 +++++++++--------- libfprint/drivers/vfs5011.c | 40 +++++++++++------------ libfprint/drivers/vfs7552.c | 54 ++++++++++++++++---------------- libfprint/fp-print.h | 2 +- libfprint/fpi-compat.h | 2 +- libfprint/fpi-log.h | 12 +++---- libfprint/fpi-ssm.h | 2 +- 10 files changed, 92 insertions(+), 92 deletions(-) diff --git a/demo/gtk-libfprint-test.c b/demo/gtk-libfprint-test.c index 68500639..30e91ef2 100644 --- a/demo/gtk-libfprint-test.c +++ b/demo/gtk-libfprint-test.c @@ -102,9 +102,9 @@ plot_minutiae (unsigned char *rgbdata, int i; #define write_pixel(num) do { \ - rgbdata[((num) * 3)] = 0xff; \ - rgbdata[((num) * 3) + 1] = 0; \ - rgbdata[((num) * 3) + 2] = 0; \ + rgbdata[((num) * 3)] = 0xff; \ + rgbdata[((num) * 3) + 1] = 0; \ + rgbdata[((num) * 3) + 2] = 0; \ } while(0) for (i = 0; i < minutiae->len; i++) diff --git a/libfprint/drivers/upeksonly.c b/libfprint/drivers/upeksonly.c index 9d93cc76..e7ea3ce2 100644 --- a/libfprint/drivers/upeksonly.c +++ b/libfprint/drivers/upeksonly.c @@ -74,26 +74,26 @@ struct _FpiDeviceUpeksonly FpiSsm *loopsm; /* Do we really need multiple concurrent transfers? */ - GCancellable *img_cancellable; - GPtrArray *img_transfers; - int num_flying; + GCancellable *img_cancellable; + GPtrArray *img_transfers; + int num_flying; - GSList *rows; - unsigned num_rows; - unsigned char *rowbuf; - int rowbuf_offset; + GSList *rows; + unsigned num_rows; + unsigned char *rowbuf; + int rowbuf_offset; - int wraparounds; - int num_blank; - int num_nonblank; - enum sonly_fs finger_state; - int last_seqnum; + int wraparounds; + int num_blank; + int num_nonblank; + enum sonly_fs finger_state; + int last_seqnum; enum sonly_kill_transfers_action killing_transfers; - GError *kill_error; - FpiSsm *kill_ssm; + GError *kill_error; + FpiSsm *kill_ssm; - struct fpi_line_asmbl_ctx assembling_ctx; + struct fpi_line_asmbl_ctx assembling_ctx; }; G_DECLARE_FINAL_TYPE (FpiDeviceUpeksonly, fpi_device_upeksonly, FPI, DEVICE_UPEKSONLY, FpImageDevice); diff --git a/libfprint/drivers/vfs101.c b/libfprint/drivers/vfs101.c index 7020726a..6963c59e 100644 --- a/libfprint/drivers/vfs101.c +++ b/libfprint/drivers/vfs101.c @@ -162,9 +162,9 @@ enum { /* Dump buffer for debug */ #define dump_buffer(buf) \ - fp_dbg ("%02x %02x %02x %02x %02x %02x %02x %02x", \ - buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13] \ - ) + fp_dbg ("%02x %02x %02x %02x %02x %02x %02x %02x", \ + buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13] \ + ) /* Callback of asynchronous send */ static void diff --git a/libfprint/drivers/vfs301_proto.c b/libfprint/drivers/vfs301_proto.c index 122a889a..6bbb3c7d 100644 --- a/libfprint/drivers/vfs301_proto.c +++ b/libfprint/drivers/vfs301_proto.c @@ -157,7 +157,7 @@ vfs301_proto_generate_0B (int subtype, gssize *len) } #define HEX_TO_INT(c) \ - (((c) >= '0' && (c) <= '9') ? ((c) - '0') : ((c) - 'A' + 10)) + (((c) >= '0' && (c) <= '9') ? ((c) - '0') : ((c) - 'A' + 10)) static guint8 * translate_str (const char **srcL, gssize *len) @@ -422,15 +422,15 @@ img_process_data (int first_block, FpDeviceVfs301 *dev, const guint8 *buf, int l /************************** PROTOCOL STUFF ************************************/ #define USB_RECV(from, len) \ - usb_recv (dev, from, len, NULL, NULL) + usb_recv (dev, from, len, NULL, NULL) #define USB_SEND(type, subtype) \ - { \ - const guint8 *data; \ - gssize len; \ - data = vfs301_proto_generate (type, subtype, &len); \ - usb_send (dev, data, len, NULL); \ - } + { \ + const guint8 *data; \ + gssize len; \ + data = vfs301_proto_generate (type, subtype, &len); \ + usb_send (dev, data, len, NULL); \ + } #define RAW_DATA(x) g_memdup2 (x, sizeof (x)), sizeof (x) @@ -489,13 +489,13 @@ vfs301_proto_peek_event (FpDeviceVfs301 *dev) * we will run into timeouts randomly and need to then try again. */ #define PARALLEL_RECEIVE(e1, l1, e2, l2) \ - { \ - g_autoptr(GError) error = NULL; \ - usb_recv (dev, e1, l1, NULL, &error); \ - usb_recv (dev, e2, l2, NULL, NULL); \ - if (g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT)) \ - usb_recv (dev, e1, l1, NULL, NULL); \ - } + { \ + g_autoptr(GError) error = NULL; \ + usb_recv (dev, e1, l1, NULL, &error); \ + usb_recv (dev, e2, l2, NULL, NULL); \ + if (g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT)) \ + usb_recv (dev, e1, l1, NULL, NULL); \ + } static void vfs301_proto_process_event_cb (FpiUsbTransfer *transfer, diff --git a/libfprint/drivers/vfs5011.c b/libfprint/drivers/vfs5011.c index b9e0587b..65c98c96 100644 --- a/libfprint/drivers/vfs5011.c +++ b/libfprint/drivers/vfs5011.c @@ -41,30 +41,30 @@ struct usb_action }; #define SEND(ENDPOINT, COMMAND) \ - { \ - .type = ACTION_SEND, \ - .endpoint = ENDPOINT, \ - .name = #COMMAND, \ - .size = sizeof (COMMAND), \ - .data = COMMAND \ - }, + { \ + .type = ACTION_SEND, \ + .endpoint = ENDPOINT, \ + .name = #COMMAND, \ + .size = sizeof (COMMAND), \ + .data = COMMAND \ + }, #define RECV(ENDPOINT, SIZE) \ - { \ - .type = ACTION_RECEIVE, \ - .endpoint = ENDPOINT, \ - .size = SIZE, \ - .data = NULL \ - }, + { \ + .type = ACTION_RECEIVE, \ + .endpoint = ENDPOINT, \ + .size = SIZE, \ + .data = NULL \ + }, #define RECV_CHECK(ENDPOINT, SIZE, EXPECTED) \ - { \ - .type = ACTION_RECEIVE, \ - .endpoint = ENDPOINT, \ - .size = SIZE, \ - .data = EXPECTED, \ - .correct_reply_size = sizeof (EXPECTED) \ - }, + { \ + .type = ACTION_RECEIVE, \ + .endpoint = ENDPOINT, \ + .size = SIZE, \ + .data = EXPECTED, \ + .correct_reply_size = sizeof (EXPECTED) \ + }, struct usbexchange_data { diff --git a/libfprint/drivers/vfs7552.c b/libfprint/drivers/vfs7552.c index 53b4d3f3..852e35b5 100644 --- a/libfprint/drivers/vfs7552.c +++ b/libfprint/drivers/vfs7552.c @@ -51,39 +51,39 @@ struct usb_action }; #define SEND(ENDPOINT, COMMAND) \ - { \ - .type = ACTION_SEND, \ - .endpoint = ENDPOINT, \ - .name = #COMMAND, \ - .size = sizeof (COMMAND), \ - .data = COMMAND \ - }, + { \ + .type = ACTION_SEND, \ + .endpoint = ENDPOINT, \ + .name = #COMMAND, \ + .size = sizeof (COMMAND), \ + .data = COMMAND \ + }, #define RECV(ENDPOINT, SIZE) \ - { \ - .type = ACTION_RECEIVE, \ - .endpoint = ENDPOINT, \ - .size = SIZE, \ - .data = NULL \ - }, + { \ + .type = ACTION_RECEIVE, \ + .endpoint = ENDPOINT, \ + .size = SIZE, \ + .data = NULL \ + }, #define RECV_CHECK(ENDPOINT, SIZE, EXPECTED) \ - { \ - .type = ACTION_RECEIVE, \ - .endpoint = ENDPOINT, \ - .size = SIZE, \ - .data = EXPECTED, \ - .correct_reply_size = sizeof (EXPECTED) \ - }, + { \ + .type = ACTION_RECEIVE, \ + .endpoint = ENDPOINT, \ + .size = SIZE, \ + .data = EXPECTED, \ + .correct_reply_size = sizeof (EXPECTED) \ + }, #define RECV_CHECK_SIZE(ENDPOINT, SIZE, EXPECTED) \ - { \ - .type = ACTION_RECEIVE, \ - .endpoint = ENDPOINT, \ - .size = SIZE, \ - .data = NULL, \ - .correct_reply_size = sizeof (EXPECTED) \ - }, + { \ + .type = ACTION_RECEIVE, \ + .endpoint = ENDPOINT, \ + .size = SIZE, \ + .data = NULL, \ + .correct_reply_size = sizeof (EXPECTED) \ + }, struct usbexchange_data { diff --git a/libfprint/fp-print.h b/libfprint/fp-print.h index ac6820d7..7a2abee3 100644 --- a/libfprint/fp-print.h +++ b/libfprint/fp-print.h @@ -29,7 +29,7 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (FpPrint, fp_print, FP, PRINT, GInitiallyUnowned) #define FP_FINGER_IS_VALID(finger) \ - ((finger) >= FP_FINGER_FIRST && (finger) <= FP_FINGER_LAST) + ((finger) >= FP_FINGER_FIRST && (finger) <= FP_FINGER_LAST) #include "fp-device.h" diff --git a/libfprint/fpi-compat.h b/libfprint/fpi-compat.h index 6582fb5a..ad86874a 100644 --- a/libfprint/fpi-compat.h +++ b/libfprint/fpi-compat.h @@ -43,7 +43,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (GDate, g_date_free); #define g_memdup2(data, size) g_memdup ((data), (size)) #else #define g_memdup2(data, size) \ - (G_GNUC_EXTENSION ({ \ + (G_GNUC_EXTENSION ({ \ G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ g_memdup2 ((data), (size)); \ G_GNUC_END_IGNORE_DEPRECATIONS \ diff --git a/libfprint/fpi-log.h b/libfprint/fpi-log.h index da612049..3231cf5f 100644 --- a/libfprint/fpi-log.h +++ b/libfprint/fpi-log.h @@ -80,12 +80,12 @@ * Uses fp_err() to print an error if the @condition is true. */ #define BUG_ON(condition) G_STMT_START \ - if (condition) { \ - char *s; \ - s = g_strconcat ("BUG: (", #condition, ")", NULL); \ - fp_err ("%s: %s() %s:%d", s, G_STRFUNC, __FILE__, __LINE__); \ - g_free (s); \ - } G_STMT_END + if (condition) { \ + char *s; \ + s = g_strconcat ("BUG: (", #condition, ")", NULL); \ + fp_err ("%s: %s() %s:%d", s, G_STRFUNC, __FILE__, __LINE__); \ + g_free (s); \ + } G_STMT_END /** * BUG: diff --git a/libfprint/fpi-ssm.h b/libfprint/fpi-ssm.h index d2601c88..ab80a26f 100644 --- a/libfprint/fpi-ssm.h +++ b/libfprint/fpi-ssm.h @@ -60,7 +60,7 @@ typedef void (*FpiSsmHandlerCallback)(FpiSsm *ssm, /* for library and drivers */ #define fpi_ssm_new(dev, handler, nr_states) \ - fpi_ssm_new_full (dev, handler, nr_states, nr_states, #nr_states) + fpi_ssm_new_full (dev, handler, nr_states, nr_states, #nr_states) FpiSsm *fpi_ssm_new_full (FpDevice *dev, FpiSsmHandlerCallback handler, int nr_states, From d492901c3e0b36de8fc11214c876b35264da5a8f Mon Sep 17 00:00:00 2001 From: Vasily Khoruzhick Date: Tue, 10 Jan 2023 10:58:46 -0800 Subject: [PATCH 08/50] tests: valgrind: generate suppressions list for new tests-suppress-new-valgrind-errors-in-python That will help with updating suppresions lists in future --- tests/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/meson.build b/tests/meson.build index 97a5bfa1..690d7a9d 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -248,6 +248,7 @@ if valgrind.found() '--track-origins=yes', '--show-leak-kinds=definite,possible', '--show-error-list=yes', + '--gen-suppressions=all', '--suppressions=' + libfprint_suppressions, '--suppressions=' + glib_suppressions, '--suppressions=' + python_suppressions, From db2fa81358dab03abf477bc9faff0383fd475483 Mon Sep 17 00:00:00 2001 From: Vasily Khoruzhick Date: Tue, 10 Jan 2023 11:09:40 -0800 Subject: [PATCH 09/50] tests: valgrind: update python and libfprint suppressions list --- tests/libfprint.supp | 7 +++++++ tests/valgrind-python.supp | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/tests/libfprint.supp b/tests/libfprint.supp index 30a41454..f2acc71a 100644 --- a/tests/libfprint.supp +++ b/tests/libfprint.supp @@ -17,3 +17,10 @@ ... } +{ + + Memcheck:Leak + fun:calloc + ... + fun:g_thread_new +} diff --git a/tests/valgrind-python.supp b/tests/valgrind-python.supp index 395e8015..59e21858 100644 --- a/tests/valgrind-python.supp +++ b/tests/valgrind-python.supp @@ -40,6 +40,20 @@ fun:_Py* } +{ + ignore__py_addr32 + Memcheck:Addr32 + ... + fun:_Py* +} + +{ + ignore__py_addr32 + Memcheck:Addr32 + ... + fun:Py* +} + { ignore_py_leaks Memcheck:Leak From 62818b9407e79c2931f03d74bff6af39bbda2a34 Mon Sep 17 00:00:00 2001 From: Vasily Khoruzhick Date: Wed, 4 Jan 2023 23:35:33 -0800 Subject: [PATCH 10/50] fpcmoc: fix use-after free in multiple callbacks Drop if statement that retrieves internal ssm->error. "error" is already a copied ssm->error, so it makes no sense to return internal copy which will be freed when ssm is marked as done. Fixes #526 --- libfprint/drivers/fpcmoc/fpc.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/libfprint/drivers/fpcmoc/fpc.c b/libfprint/drivers/fpcmoc/fpc.c index b64cc36e..0f2b5591 100644 --- a/libfprint/drivers/fpcmoc/fpc.c +++ b/libfprint/drivers/fpcmoc/fpc.c @@ -1149,12 +1149,9 @@ fpc_enroll_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) 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); + fpi_device_enroll_complete (dev, NULL, g_steal_pointer (&error)); self->task_ssm = NULL; return; } @@ -1336,9 +1333,6 @@ fpc_verify_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) fp_info ("Verify_identify 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) @@ -1348,9 +1342,9 @@ fpc_verify_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) } if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_VERIFY) - fpi_device_verify_complete (dev, error); + fpi_device_verify_complete (dev, g_steal_pointer (&error)); else - fpi_device_identify_complete (dev, error); + fpi_device_identify_complete (dev, g_steal_pointer (&error)); self->task_ssm = NULL; } @@ -1448,10 +1442,7 @@ fpc_clear_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) fp_info ("Clear Storage complete!"); - if (fpi_ssm_get_error (ssm)) - error = fpi_ssm_get_error (ssm); - - fpi_device_clear_storage_complete (dev, error); + fpi_device_clear_storage_complete (dev, g_steal_pointer (&error)); self->task_ssm = NULL; } @@ -1555,10 +1546,7 @@ fpc_init_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) { FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (dev); - if (fpi_ssm_get_error (ssm)) - error = fpi_ssm_get_error (ssm); - - fpi_device_open_complete (dev, error); + fpi_device_open_complete (dev, g_steal_pointer (&error)); self->task_ssm = NULL; } From fafe70f985e3386f3be2d1075270bc169c34b22f Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 11 Oct 2022 15:54:53 +0200 Subject: [PATCH 11/50] upektc_img: Fix CRC for upek2020_init_capture. --- libfprint/drivers/upektc_img.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libfprint/drivers/upektc_img.h b/libfprint/drivers/upektc_img.h index 3052b652..4af8ebf0 100644 --- a/libfprint/drivers/upektc_img.h +++ b/libfprint/drivers/upektc_img.h @@ -75,7 +75,7 @@ static const unsigned char upek2020_init_capture[] = { 0x02, 0x00, /* Wait for acceptable finger */ 0x02, - 0x14, 0x9a /* CRC */ + 0x25, 0xa9 /* CRC */ }; #if 0 From 522b481297560cabbf2a18ba1aabdb11e0eb690f Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 11 Oct 2022 17:49:00 +0200 Subject: [PATCH 12/50] upektc_img: Add sensor chip detection Move setting up sensor type and image dimensions to activation time. --- libfprint/drivers/upektc_img.c | 83 +++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/libfprint/drivers/upektc_img.c b/libfprint/drivers/upektc_img.c index 19bc4158..fc5bfe78 100644 --- a/libfprint/drivers/upektc_img.c +++ b/libfprint/drivers/upektc_img.c @@ -31,10 +31,6 @@ static void start_deactivation (FpImageDevice *dev); #define CTRL_TIMEOUT 4000 #define BULK_TIMEOUT 4000 -#define IMAGE_WIDTH 144 -#define IMAGE_HEIGHT 384 -#define IMAGE_SIZE (IMAGE_WIDTH * IMAGE_HEIGHT) - #define MAX_CMD_SIZE 64 #define MAX_RESPONSE_SIZE 2052 #define SHORT_RESPONSE_SIZE 64 @@ -47,8 +43,10 @@ struct _FpiDeviceUpektcImg unsigned char response[MAX_RESPONSE_SIZE]; unsigned char *image_bits; unsigned char seq; + size_t expected_image_size; size_t image_size; size_t response_rest; + gboolean area_sensor; gboolean deactivating; }; G_DECLARE_FINAL_TYPE (FpiDeviceUpektcImg, fpi_device_upektc_img, FPI, @@ -180,6 +178,7 @@ capture_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { FpImageDevice *dev = FP_IMAGE_DEVICE (device); + FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_GET_CLASS (dev); FpiDeviceUpektcImg *self = FPI_DEVICE_UPEKTC_IMG (dev); unsigned char *data = self->response; FpImage *img; @@ -307,13 +306,13 @@ capture_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, self->image_size += upektc_img_process_image_frame (self->image_bits + self->image_size, data); - BUG_ON (self->image_size != IMAGE_SIZE); + BUG_ON (self->image_size != self->expected_image_size); fp_dbg ("Image size is %lu", (gulong) self->image_size); - img = fp_image_new (IMAGE_WIDTH, IMAGE_HEIGHT); + img = fp_image_new (img_class->img_width, img_class->img_height); img->flags |= FPI_IMAGE_PARTIAL; memcpy (img->data, self->image_bits, - IMAGE_SIZE); + self->image_size); fpi_image_device_image_captured (dev, img); fpi_image_device_report_finger_status (dev, FALSE); @@ -513,15 +512,77 @@ init_reqs_cb (FpiUsbTransfer *transfer, FpDevice *device, fpi_ssm_mark_failed (transfer->ssm, error); } -/* TODO: process response properly */ static void init_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { + FpImageDevice *dev = FP_IMAGE_DEVICE (device); + FpiDeviceUpektcImg *self = FPI_DEVICE_UPEKTC_IMG (dev); + unsigned char *data = self->response; + if (!error) fpi_ssm_next_state (transfer->ssm); else fpi_ssm_mark_failed (transfer->ssm, error); + + if (data[12] == 0x06 && data[13] == 0x14) /* if get_info */ + { + FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_GET_CLASS (dev); + uint16_t width = 0; + +#if 0 + if (data[46] == 0x26 && data[47] == 0x00 && data[48] == 0x00 && data[49] == 0x66) /* Sensor type 66000026 = TCS1s */ + fp_dbg ("Sensor type : TCS1s"); +#endif + + width = (data[51] << 8) | data[50]; + + switch (width) + { + case 256: + fp_dbg ("Sensor type : TCS1x"); /* 360x256 --- 270x192 must be set*/ + img_class->img_width = 192; + img_class->img_height = 270; + self->area_sensor = TRUE; + break; + + case 208: + fp_dbg ("Sensor type : TCS2"); /* 288x208 --- 216x156 must be set*/ + img_class->img_width = 156; + img_class->img_height = 216; + self->area_sensor = TRUE; + break; + + case 248: + fp_dbg ("Sensor type : TCS3"); /* 360x248 --- 270x186 must be set*/ + img_class->img_width = 186; + img_class->img_height = 270; + self->area_sensor = FALSE; + break; + + case 192: + fp_dbg ("Sensor type : TCS4x"); /* 512x192 --- 384x144 must be set*/ + img_class->img_width = 144; + img_class->img_height = 384; + self->area_sensor = FALSE; + break; + + case 144: + fp_dbg ("Sensor type : TCS5x"); /* 512x144 --- 384x108 must be set*/ + img_class->img_width = 108; + img_class->img_height = 384; + self->area_sensor = FALSE; + break; + + default: + fp_dbg ("Sensor type : Unknown"); + break; + } + + BUG_ON (img_class->img_width == -1 || img_class->img_height == -1); + self->expected_image_size = img_class->img_width * img_class->img_height; + self->image_bits = g_malloc0 (self->expected_image_size * 2); + } } static void @@ -616,7 +677,6 @@ dev_deactivate (FpImageDevice *dev) static void dev_init (FpImageDevice *dev) { - FpiDeviceUpektcImg *self = FPI_DEVICE_UPEKTC_IMG (dev); GError *error = NULL; /* TODO check that device has endpoints we're using */ @@ -627,7 +687,6 @@ dev_init (FpImageDevice *dev) return; } - self->image_bits = g_malloc0 (IMAGE_SIZE * 2); fpi_image_device_open_complete (dev, NULL); } @@ -687,6 +746,6 @@ fpi_device_upektc_img_class_init (FpiDeviceUpektcImgClass *klass) img_class->bz3_threshold = 20; - img_class->img_width = IMAGE_WIDTH; - img_class->img_height = IMAGE_HEIGHT; + img_class->img_width = -1; + img_class->img_height = -1; } From cda4e6136c5676cd90dbc13307565eadbdd1a904 Mon Sep 17 00:00:00 2001 From: Toni Date: Wed, 12 Oct 2022 07:51:54 +0200 Subject: [PATCH 13/50] upektc_img: Fix protocol for area sensors --- libfprint/drivers/upektc_img.c | 34 +++++++++++++++++++++++++++------- libfprint/drivers/upektc_img.h | 16 ++++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/libfprint/drivers/upektc_img.c b/libfprint/drivers/upektc_img.c index fc5bfe78..7d15cc42 100644 --- a/libfprint/drivers/upektc_img.c +++ b/libfprint/drivers/upektc_img.c @@ -246,12 +246,25 @@ capture_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, CAPTURE_ACK_00_28); break; + case 0x13: + /* finger is present keep your finger on reader */ + fpi_ssm_jump_to_state (transfer->ssm, + self->area_sensor ? + CAPTURE_ACK_00_28 : CAPTURE_ACK_00_28_TERM); + break; + case 0x00: /* finger is present! */ fpi_ssm_jump_to_state (transfer->ssm, CAPTURE_ACK_00_28); break; + case 0x01: + /* no finger! */ + fpi_ssm_jump_to_state (transfer->ssm, + CAPTURE_ACK_00_28); + break; + case 0x1e: /* short scan */ fp_err ("short scan, aborting"); @@ -260,18 +273,20 @@ capture_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, fpi_image_device_report_finger_status (dev, FALSE); fpi_ssm_jump_to_state (transfer->ssm, - CAPTURE_ACK_00_28_TERM); + self->area_sensor ? + CAPTURE_ACK_00_28 : CAPTURE_ACK_00_28_TERM); break; case 0x1d: - /* too much horisontal movement */ - fp_err ("too much horisontal movement, aborting"); + /* too much horizontal movement */ + fp_err ("too much horizontal movement, aborting"); fpi_image_device_retry_scan (dev, FP_DEVICE_RETRY_CENTER_FINGER); fpi_image_device_report_finger_status (dev, FALSE); fpi_ssm_jump_to_state (transfer->ssm, - CAPTURE_ACK_00_28_TERM); + self->area_sensor ? + CAPTURE_ACK_00_28 : CAPTURE_ACK_00_28_TERM); break; default: @@ -282,7 +297,8 @@ capture_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, fpi_image_device_report_finger_status (dev, FALSE); fpi_ssm_jump_to_state (transfer->ssm, - CAPTURE_ACK_00_28_TERM); + self->area_sensor ? + CAPTURE_ACK_00_28 : CAPTURE_ACK_00_28_TERM); break; } break; @@ -345,8 +361,12 @@ capture_run_state (FpiSsm *ssm, FpDevice *_dev) switch (fpi_ssm_get_cur_state (ssm)) { case CAPTURE_INIT_CAPTURE: - upektc_img_submit_req (ssm, dev, upek2020_init_capture, sizeof (upek2020_init_capture), - self->seq, capture_reqs_cb); + if (self->area_sensor) + upektc_img_submit_req (ssm, dev, upek2020_init_capture_press, sizeof (upek2020_init_capture_press), + self->seq, capture_reqs_cb); + else + upektc_img_submit_req (ssm, dev, upek2020_init_capture, sizeof (upek2020_init_capture), + self->seq, capture_reqs_cb); self->seq++; break; diff --git a/libfprint/drivers/upektc_img.h b/libfprint/drivers/upektc_img.h index 4af8ebf0..1c3a6949 100644 --- a/libfprint/drivers/upektc_img.h +++ b/libfprint/drivers/upektc_img.h @@ -78,6 +78,22 @@ static const unsigned char upek2020_init_capture[] = { 0x25, 0xa9 /* CRC */ }; +static const unsigned char upek2020_init_capture_press[] = { + 'C', 'i', 'a', 'o', + 0x00, + 0x00, 0x0e, /* Seq = 7, len = 0x00e */ + 0x28, /* CMD = 0x28 */ + 0x0b, 0x00, /* Inner len = 0x000b */ + 0x00, 0x00, + 0x0e, /* SUBCMD = 0x0e */ + 0x02, + 0xfe, 0xff, 0xff, 0xff, /* timeout = -2 = 0xfffffffe = infinite time */ + 0x02, + 0x01, /* Wait for finger */ + 0x02, + 0x14, 0x9a /* CRC */ +}; + #if 0 static const unsigned char finger_status[] = { 'C', 'i', 'a', 'o', From 987f23698e52c12f53b55e8474e2d9f5d0d05429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20B=C3=B6sz=C3=B6rm=C3=A9nyi?= Date: Wed, 12 Oct 2022 13:08:30 +0200 Subject: [PATCH 14/50] upektc_img: Deduce sensor type from the information packet --- libfprint/drivers/upektc_img.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/libfprint/drivers/upektc_img.c b/libfprint/drivers/upektc_img.c index 7d15cc42..3e2e7d95 100644 --- a/libfprint/drivers/upektc_img.c +++ b/libfprint/drivers/upektc_img.c @@ -550,11 +550,7 @@ init_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_GET_CLASS (dev); uint16_t width = 0; -#if 0 - if (data[46] == 0x26 && data[47] == 0x00 && data[48] == 0x00 && data[49] == 0x66) /* Sensor type 66000026 = TCS1s */ - fp_dbg ("Sensor type : TCS1s"); -#endif - + self->area_sensor = !(data[49] & 0x80); width = (data[51] << 8) | data[50]; switch (width) @@ -563,35 +559,30 @@ init_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, fp_dbg ("Sensor type : TCS1x"); /* 360x256 --- 270x192 must be set*/ img_class->img_width = 192; img_class->img_height = 270; - self->area_sensor = TRUE; break; case 208: fp_dbg ("Sensor type : TCS2"); /* 288x208 --- 216x156 must be set*/ img_class->img_width = 156; img_class->img_height = 216; - self->area_sensor = TRUE; break; case 248: fp_dbg ("Sensor type : TCS3"); /* 360x248 --- 270x186 must be set*/ img_class->img_width = 186; img_class->img_height = 270; - self->area_sensor = FALSE; break; case 192: fp_dbg ("Sensor type : TCS4x"); /* 512x192 --- 384x144 must be set*/ img_class->img_width = 144; img_class->img_height = 384; - self->area_sensor = FALSE; break; case 144: fp_dbg ("Sensor type : TCS5x"); /* 512x144 --- 384x108 must be set*/ img_class->img_width = 108; img_class->img_height = 384; - self->area_sensor = FALSE; break; default: From 29a24ba67fab2b1155758d86e8db4e0a83276961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20B=C3=B6sz=C3=B6rm=C3=A9nyi?= Date: Thu, 13 Oct 2022 10:57:02 +0200 Subject: [PATCH 15/50] upektc_img: Add finger status changes --- libfprint/drivers/upektc_img.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libfprint/drivers/upektc_img.c b/libfprint/drivers/upektc_img.c index 3e2e7d95..c387d4b8 100644 --- a/libfprint/drivers/upektc_img.c +++ b/libfprint/drivers/upektc_img.c @@ -248,6 +248,9 @@ capture_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, case 0x13: /* finger is present keep your finger on reader */ + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); fpi_ssm_jump_to_state (transfer->ssm, self->area_sensor ? CAPTURE_ACK_00_28 : CAPTURE_ACK_00_28_TERM); @@ -255,12 +258,18 @@ capture_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, case 0x00: /* finger is present! */ + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NONE); fpi_ssm_jump_to_state (transfer->ssm, CAPTURE_ACK_00_28); break; case 0x01: /* no finger! */ + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NONE, + FP_FINGER_STATUS_PRESENT); fpi_ssm_jump_to_state (transfer->ssm, CAPTURE_ACK_00_28); break; From 9c900789dedadefb92a6f0d5fe0aba2db1029a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20B=C3=B6sz=C3=B6rm=C3=A9nyi?= Date: Thu, 13 Oct 2022 11:04:01 +0200 Subject: [PATCH 16/50] upektc_img: Add asserts for expected height reported by the device --- libfprint/drivers/upektc_img.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/libfprint/drivers/upektc_img.c b/libfprint/drivers/upektc_img.c index c387d4b8..0fa56d24 100644 --- a/libfprint/drivers/upektc_img.c +++ b/libfprint/drivers/upektc_img.c @@ -557,39 +557,44 @@ init_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, if (data[12] == 0x06 && data[13] == 0x14) /* if get_info */ { FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_GET_CLASS (dev); - uint16_t width = 0; + uint16_t width = (data[51] << 8) | data[50]; + uint16_t height = (data[53] << 8) | data[52]; self->area_sensor = !(data[49] & 0x80); - width = (data[51] << 8) | data[50]; switch (width) { case 256: - fp_dbg ("Sensor type : TCS1x"); /* 360x256 --- 270x192 must be set*/ + fp_dbg ("Sensor type : TCS1x, width x height: %hu x %hu", width, height); /* 360x256 --- 270x192 must be set */ + BUG_ON (height != 360); img_class->img_width = 192; img_class->img_height = 270; break; case 208: - fp_dbg ("Sensor type : TCS2"); /* 288x208 --- 216x156 must be set*/ + fp_dbg ("Sensor type : TCS2, width x height: %hu x %hu", width, height); /* 288x208 --- 216x156 must be set */ + BUG_ON (height != 288); img_class->img_width = 156; img_class->img_height = 216; break; case 248: - fp_dbg ("Sensor type : TCS3"); /* 360x248 --- 270x186 must be set*/ + fp_dbg ("Sensor type : TCS3, width x height: %hu x %hu", width, height); /* 360x248 --- 270x186 must be set */ + BUG_ON (height != 360); img_class->img_width = 186; img_class->img_height = 270; break; case 192: - fp_dbg ("Sensor type : TCS4x"); /* 512x192 --- 384x144 must be set*/ + fp_dbg ("Sensor type : TCS4x, width x height: %hu x %hu", width, height); /* 512x192 --- 384x144 must be set */ + BUG_ON (height != 512); img_class->img_width = 144; img_class->img_height = 384; break; case 144: - fp_dbg ("Sensor type : TCS5x"); /* 512x144 --- 384x108 must be set*/ + fp_dbg ("Sensor type : TCS5x, width x height: %hu x %hu", width, height); /* 512x144 --- 384x108 must be set */ + BUG_ON (height != 512); img_class->img_width = 108; img_class->img_height = 384; break; From bfaa9a92418b9517daf2e71c01b112813689cd13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20B=C3=B6sz=C3=B6rm=C3=A9nyi?= Date: Thu, 13 Oct 2022 15:24:34 +0200 Subject: [PATCH 17/50] upektc_img: Fix asynchronous problem in init_read_data_cb() --- libfprint/drivers/upektc_img.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libfprint/drivers/upektc_img.c b/libfprint/drivers/upektc_img.c index 0fa56d24..8898c5c2 100644 --- a/libfprint/drivers/upektc_img.c +++ b/libfprint/drivers/upektc_img.c @@ -549,10 +549,11 @@ init_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, FpiDeviceUpektcImg *self = FPI_DEVICE_UPEKTC_IMG (dev); unsigned char *data = self->response; - if (!error) - fpi_ssm_next_state (transfer->ssm); - else - fpi_ssm_mark_failed (transfer->ssm, error); + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } if (data[12] == 0x06 && data[13] == 0x14) /* if get_info */ { @@ -608,6 +609,8 @@ init_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, self->expected_image_size = img_class->img_width * img_class->img_height; self->image_bits = g_malloc0 (self->expected_image_size * 2); } + + fpi_ssm_next_state (transfer->ssm); } static void From 5e52ad2ad105e8f37d44b7b55747b0471f1b40a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20B=C3=B6sz=C3=B6rm=C3=A9nyi?= Date: Thu, 13 Oct 2022 15:44:09 +0200 Subject: [PATCH 18/50] upektc_img: Add new unit test for the TCS1s chip variant --- tests/meson.build | 1 + tests/upektc_img-tcs1s/capture.pcapng | Bin 0 -> 73728 bytes tests/upektc_img-tcs1s/capture.png | Bin 0 -> 75525 bytes tests/upektc_img-tcs1s/device | 308 ++++++++++++++++++++++++++ 4 files changed, 309 insertions(+) create mode 100644 tests/upektc_img-tcs1s/capture.pcapng create mode 100644 tests/upektc_img-tcs1s/capture.png create mode 100644 tests/upektc_img-tcs1s/device diff --git a/tests/meson.build b/tests/meson.build index 690d7a9d..a8761750 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -30,6 +30,7 @@ drivers_tests = [ 'elanspi', 'synaptics', 'upektc_img', + 'upektc_img-tcs1s', 'uru4000-msv2', 'uru4000-4500', 'vfs0050', diff --git a/tests/upektc_img-tcs1s/capture.pcapng b/tests/upektc_img-tcs1s/capture.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..94f633411314068f0d42a04cdcda2b6464d13b73 GIT binary patch literal 73728 zcmb@u2b^73c`rJr_jC3>yZ3f>JH3}VeR{8&CXKA#Em@XrA=|Q$3&zF;(`+D@Ac_eP z458#gfPfQjUP36rgyJUK!j$y;?n{0*_l5V8xAthpl7>OC{qFkBnsd%BU)kTc*7~n+ zeQWzeA^(?pdU_@s3EBoe|6{$_dx|~X$pLZh_B#%o5G!J_n$3=gZ#i=7VR3HH{sT9i z5Qk6gIeGZVzFS0N?dbgcU@@IH9X(vn#E}!Xoe@jvTrHg|rt;Zr`ry7oIen&D-c>FF z9?qUZ&%kZRPwv}uQZ$y1oH}sJ;XS90nPMSbPFF=^_{go|zMGC82Osv4F`p}1)8v4OwKyf*9906aV3!nOJ@uAyISq0?Ew)6&TYuCofz32@o?1rBV| z>BhCIEmLKj7 zKKYF<_-@sw|D9X+!yd~IU-OfXnxQp4EorRU34#x&_3bx{MSJtJ{D0vmUwf7tJo@bx zk5p^J7n_;T8nLaZUg5KQ3;M^SSLpZDkKXdCWh=+s7X8=S(o=xGWvk8=QrCXlWUCA7 zXRT-SB|!f?_-)k<`u&&bzxIs&WnKObxWuZzQP%1AAK7!0qi4;qPapYTzj4)ndf)=M z!g{`E)~~eW`)45EBKU39j=G`0-O~Rx`3l*~`s3gltNylP$>;0|*?NZ7t^iG>r|0=U zxy)~~T_xnobKl_G?^xsenqBt*zE-&&PwV_{P&d=R>ni%^;~RQiYu7(s*Q*2W-?CiC zuFnAa=fO>Db)EiZyTZB4@^`>>R{f1~m0ew+M_KsrZ(Q-Z4qO0NS^8hy--xveO zton3mL;q{-dIZR4`L!QhW7XeQEW3Jue9co^_jemHcn6?wmCky#zF$+WYLEZB``PV4KC6$l>iO)3{@1q053cKP^>^3n z^Iq#`zrH5$T0gsx*}9*75b*5)dRE=wySZ(Cd;Pgwk0-c&%eMK6tLS$s8+u*aHh=m5 z!>+GhW!DY*o9(JJUcX)ccl5I-t%m`MHGck2AfJWxt=f~{(EnPymVta$+l0U+R{d?o zvMT^yh7(-ic0T=+lh!AwyFs;z#Sas%*mHy5W_vEIKacD2vgqvwbu)eaYWl4WeXh0V zp>=&a;QlSkdOO+;PFnOYfNB*>erW8fcDhb~vpr`Hy?%T4^i&LM&gMpueLXI#hQY6W zHV=5x9*b!YzxKbUN9*Z1*ke0vb&jBiboBfW>;m{^$#*p#dIre11ddtt>Ewp~*V+?4 zeA%7^xW=l#t*qG-3L61%LD>H9zys_yJO4*({5C&#`}h^RZt&e~*FM14D%ay_o!<@W zX8NV8>HikH-nOn+2i(79*<{z<>layeo!)}}X1l&~UH&a;TL-U0|MsispD%3K^wJ`5$`bieGNf-z@)k{^e7nx)1#r%?-_>(+ z>4#5#V-_5<>Un8H|7-nn&wsz{mloeO-r#eqvgVhf*Kk=Uv$lB(d|13T`^+@p*#XC` zy1{d^PreiIw957PS?70yx|#m%SJ8j^wZ3zcP1kkmfcv*B*YTNmZ9#vtO`lnpe@oid z!Ryff($(}c8#cLC{(pIc@?TgN#PZ3b-}vNfbBw3g>2JvYFY9rhqaRr7(AI3!S-wH?*Z1zdtH$QpBC;xGM{(pH6*Rvs??}N6rvF~;HWcD?m z{If>@zpZ@oB;eTr=d5~uo#%#6Zty$_cv|Jn_~bXQrhjQer^`0op#RNvojPk=w)l4) zpLuo*`kQU~?7IA0P`-TVI`seTYWjb-wfz6~2Iar7E{NrmXW#nNYd(2?o&IL|&u+Io z<+6=BYu`5gyV@tSSNr5^*WI}9wXeVZ%LQ<1vrp#EKlPeVc5)kS@^U}9VZYoC_v`n` z&TBq7FbDW;<&(Pr&ki`ZR@ZrM_Q_pw)ny60ffwII`3(_e;*{8+j;I%xJnOtGvbs z?KYRX^S)zqE0$aqr`@1h#gcCykgo&2TlM_^v$g&90r{*s!hgFATEUf@$|n6|-pl7L z`X77zsc$&JZ>w(5->m<~ZoaI)MSn{k>-5KrfWzh_@{PG7;kCIvfm~~FZoXP(F|J;$ z@4Ml{=hApaHMdu6Hk;ii6!%OXzGDVMT)v6Br{ydL?u`}0E<{58LbGBAkHv4d*&=Ab zHFWCCcm)D?t(Acx)g*0xzOLCEf;YVS-j6-?=P&-^m5=qk=XcMQ!ZurXu5j|1|M4&X z`12Qk{;emER%)ZGgDJ=!)l=DWZ?2yYWL8rc;^%mkj2D^}G3>Q@ic_tT$?eDYv?73m zzqDh#zvQ>OY$B&Or!*0FdL|F{ujFS|qUgjZU+fRr?GA6cw>>brXHXI$8&;3AvOnau zu^GzY=QT5_EWrpANcO8 zul&WCx4eCiO4@AVk>%kJ{{27v?ce;(rBB~;Y-zaKztpFPp#Xx5y`>@@3KPL_gw-dQZc9AtLMFB+Qm9rBhdL7os>Uoq zf`XD%>OxkFxb1EZ3EM)DlVtq%09Bkx<8h^eA!*1KB(|?ME0QZ32vBkQrSUz}TORv>p?vO76 zk)>ujZTKTF?2_whx;r+TAOnD=(`OF^e5D)CpDkFDg_B1Ak#e*U2u9`nK!=^s*}-Ah zYm;v0U~u+8Cd3n@Ci@c0P^uyCVz=J46f zv=9;rMUAQbh4>9)PMZxXRxqdK1e_dnz;2rZ!U=_jtV6JZ!yP9?=JyJQNAJ z_(T{LSRqMA96qlXpO{wyHWxj+bYub(0b>^=lB609vvYfDeeZbpCmwxp-e2grY^r3A zKJbHwo|#Uey!v>% z&~Eym7&&+U=f3mY%g=^w=*;Z+U_}ffi9AcjDxJm20vz-K`GMON=JrmN1;5SV7mk0718}G|n4&@5>tv1gP{i)`69Ebdp}`9yKvB; zRj=J`x@>msU3WL~bFdqRwml-cg@CRmT3E8cR~)W8tq zwlU?gaLDF{a6YNPK|Cu$Lho!xBt#M+Yf$sUi!`Le^eE=Bxr@LLp~*@{k%08Tbiojw z9q%&XuwBRoiQ4gdQ+loy3E12=8{x%Cw-crv&eW+de5#Ka&Lf`S@z4In#b3OXhfvgz z6U}Zi$p_G=pv3!o6QVBCUJqYvClP9NyqWL>BdigF9Z)n7U{c8vgN6f$pfOURS%bq- z8(f*axzO2(Ic?EIWvvtU2k5~REe;ogSh}1QO(PgZSQE-0Zz&KQ3A!CFuZG&}K_%+; zxI9jq)9ZKoZ2HcA$}htRXmP>D$96_V%;s==V4vR?g8ht_f<5Z;C%*WBx!vgiZ9VAxuQo8qwYa!;>M{ng)@ZJ`PO~caX(7M@Z+a0O3p(BF)w z)LSph-vReo^*72o{k3Pc^y}D&iDN47vIRXfW_NphA-f0RK?mjY*c~=U{}>ZM{9e#T zh>`caLnN$zE)XXiF53k_c(eY;fP5Wre64QeN^F`B90T&bSv>j2AGv}jU(1zPU$fqr zZ~XDu%liLkcoG342?GcD)`vd**&~&bfOrpn_WyX{;!hRW?_(vozP&eBki3p4M7sRa z@ZRBYgb0>%aw@}Di{)5O;uADMSE-;tie&ECE(2v462;=>)zw0RO$OaWgihAeG#)M! zpc?_5p<5`?6comME?p@Qq|YNU_5dcP1z6M-&_6;d4@N=ofbCHd^4aW21dOEO&PB#f zP%fK?$esCvzO@d*iFjRLbWmLg=s;lMrjLH|zl<>?&D`~c@BZY{j|DH~33|fP#B3oA zhIvOgiV2C)g(*f7y||=kEHBX_L{oGgiu!rQ>xVcTs-7RfL*59Curk*jYR^CphYKc> zvxCVflY|&Iz)%i*AW?$lh##SZRu1#HU6naJ2Wm&-&itu`|Dl$ec?f2NxEEl#p0aN14XAZy~ zr{Cd@#_+jqX2=a5k%ubI^=8xzt4#GpeF0B6v5S*EZkaErku(?wLL31>05bSpHWw?E z4vj2zt^U{M6axgC&=d!Neo#!;r9l;Bo_>_ z0XtzFuEruZDWG!E&;V`0&`=oHBxYYyQe?DTA^jmpuGd7=?#B6aD<^Xj=Kw&N1xV3Ovdxaz5!_M$!udBGD49vch>?+6*o#Aql4xuXF~ zoss<89@u{W7ykOCS6=wg@uJu1%qwIjRxl;na!HTV%k~{QP}GosKjh`2(J0m|!~w4H zd7}g&$XOD?>zMD7r9ds=Z-Y3qlx+Fa}G60+^;Y3dNS$01pzN zCU3x_4VA%nH4AJ8GzZnofu186S9~s~8%~!`Z5xhy+~AZiHkGjqVDrF?mY|(pUjV3O zx8-){wmtd#@B91@pMUw~Pu(*XaC#kXUyexE`toGR%?gN}YtHmbj3FVY-{lR%-1wl# zd!0VW4JVUk*q`;eLTIfBn=wy>w4T1r#-zaDA54tqcn!q}m*4LvU?y%xAy`b-#AZ6* z0+Sxg0Q?t8xc^{@Zn#aCZ`&s(>lP8%TY2^W@D8x-OTh!KZ}8J|o+ir}@AJ|wDc zKeEk;I3a*Ed58c>~4KnuxKS#Q@wcF|bCPeMT-ZohBfPMj?O1P(4(nuT;eQ zg#NkOS_fnIGShFlJ3)o)c&3)d=(H>Yn+Fg$B+_I$Q&Z(|Fb6QPRbU2^maJJga8EZH zh`~TcWRG`1e`WIDGtofmvkW#!d*@;(w^_KF&XlPNCHjn zxNlB`FgVuWa8`)MC0{t?gE~WFUA-W~*1&1AxfwGzmWCy%l2co1oJ9aaENfXZ6zWZ- zsAQ&7PKCkLz~Lv&=KkC3V8-PTIF7>-O$M~im`uPd7Rc4(v0m5#oP4c#LU?4gWqq}S zb`(fxf#BuAB`{qiJ5T=Q4}bLXi-%VBj8wCrAwh)Ex6;WbWE2MND9D(Gkxh|kkPxWc zw%Zruyy?+yEk|f6DkXv-o`UXwOR>_O*1SHC7u+6>#tI7sB@&cku-eWl7^TZCGSLu_ z7?YNSBrEF_qjqMdRb4d+;wt?H7_EB=FWD$(1^(qyPx!MQYNEo1{h0_ z^5{&bk)2rF3QxvAdIeARytejnEuM_u^#<_dZd=y(yIbSQ5x{E$?`-hej3-9`FRQ#6 zJbBMm^gG3knDMoE@}6~FEIGC;*TIvyk6*!)8}v8h$=qAt0G>Sb>sTR@ePk&lwdL4A z5_a2ReJsg0=Cfb|?FYTR2MlnXPQvf?!jW*iTxXPQQi1eaGu#Cr{AN6P6v)>B$F2Ib zwzd98fqZWkPu~BTD|m8){$~B}fBWmhlQA`|FkfD~nSNJu_r7s9x2AR3~Fp^8r^hj%cIha+&e@ zf>hM+^4NTIW@HTZh5Z=Hb_WeQ-Kx~u_bnTIrkbFcDCjA0E>=8r`w&Zb#0j6%=?0D0 z?zdBoOaYIuNW^aQh+r`bQ{opq*zVgb? z8&da{+h;BL0c;>Ut%;_nCgP~W%Z9a#LQ0IwFOKH5T4~zERaFVWT(MHB_*^6-q()Eo z4fB}Y7v^x#F**En?yh+>LNGYjmxO{0Ss7bCGRh!$A;m~qh~T=ZWNK)v7xjh7-ZDhM zpf7QGgQ3Pq3W`!N>haoK(SEI2DaS)=3ZM)YaysL?VV}q5^?*QTyR9 z{OD&t|HVoi$xcqj5j%)kderfDrPfTqp{T2|n}ustnoH)>rkPF&@!`dij`Lh1)=VX` z{dk1px$(I~af)SCGa8Nf;4s8Wqo=!)!pO<7WSCX_Q7$vphp`@)zktxVR9NX(b4eVC zAN7W}^BM*4p*`SmIJ0~>fICU89YMp9s4_P?8U&^RlQcfY`&`tW`-1>(THz$O-422@ zZWonM!uH)yedooWUb?jrgA(mV4YzrnZX#3G)%khR6@ioe1j!^uRi#_YDJDQS^!7dq zl6f-`XLX(vGCJzv(?)Ucbf#D#!w|%fh=8Z3?@H1vDHkeIJ&%GwQL2?MfU!}DM&-ek zvAMQEM1u$%bOxvDRKRTy2Av2PM%kc1c>VEYrlf%WxSr0SPEQ1kw9WwSLCN9&(uP7I zmj{W$UWYG)gg~odA>W~=o_Y2cFF(=`k#xGzHYEuOdhj?y75COz5N*NSEQItnnAvR& zJ{1Sy*1F-vG+x2b%C?$b8H#HZsmIcj?-}I6P?$iWU>HQ9xxTXB=O-~HR%H+-j098N zj4a8fFPMDjT{mqzSa5*Qh!72di8j@9cRQ_p4gQ$3J`)sQ$s1*Ot6$c%!wsg!a}i1iS!N(#D{7jKN|6& zdI-W$3k2Fy002a|$L={foacOC&dVuLKI^j0z!YZ63DwM``;!!tY79&+>ya>#QVBp7 zO#eVk2^ORV73Clg8GsWLc~h+3)Qn{+{Zk2ffVup*Z_X^_8qEbmODyqF-r>(G+SYcg9yX<_^?w^ zO}P-OmDPxkHKP)Phx{qffQqUHVyNl?-Y>#~PrOY>BoXwdWZEod8?x0sjeqjnKYH$^ z4?KA1(K0}8B;@f1lHGE(rRE2wIvoVxzAuHt={u)ZhO%)n6mY-^fro-L5lE7FhLMLJ zI<>4oG8$B|>bn+d5MeFi*u5cMVNPu)gAf|PBRH2<1Tj<{nI4|&YP2!jk>>{!dembuXr@X~`PSC0;4GBS-4E?=zP*BL1Cc)AbF!25>;E?unGMrY?%2PoPNASguwlW!vE5>t&+{WwKC!1x3rCJHAVT#0in1GH6R zx-l}XOJuB-=NfD`NwO(E&k#wBY|YdSIX%=ZDp(^#;B0-W$PiKp!iSd)>Dzy|6`uS# zzzAaie^|BCyMaNj#gjh=7{S6I7RI|C&RdJQowQ|-Ufdc_N>5$EI~%+<<4Ng(H;*Tm zuA<-bTJHU|cybBQw{+{zJbu40c$1) zg2oH`S2W;N%U}BLb1%HI*y>%FD(GODOCqCcwYhx5h#C@7nM8smF96{;<4O8+SDx?r z@`f$1)t`RHo5z#y{qhw&xj}!k{_lPF>%)@)SdAg2=8hvf&KR_ZCP`f(1)2g~7+G97 z)LiKE$3!ZCnSA2F0E_6|I9G$C2%kXvZpdj$!0B_@!p;y=TWT_4Aq-Xy@c3I>*;GUp zxws)Snk4UR!eFH0NP1v*Q`6&(Oi2j!VGaiZE~u*Y8b``9D`jy{XlVv@2bv*g1lAG* z3D!hx%U}56kALzK5o`2M)p=g6qdr!PA3nA+-W4NJu9j0}Dv&gh*u=SaE=sMMNx*;8V1gnRiQkf&=|rooz=1!2|9gYw^t}l^kx(+7)nAg z0q;6B(~czbqN$~{%=YmC74ZgCk!A4mSQ6n>w;u44TGWSxy=1SFHz2T5r(_RJ1zh>_ zeIW&)4Y0BrPsbZGJHq=v_x+!~@ONPPY$U1!X2J+NaWl4h=EiLWiC}zSSw|8v5Y(h9 z!-sE5727&MmQ-?E1>?q(c!aK38%lWy3+SR2^=1{lJ~!9Oimj755IA5Yt~RE?VZS>P z!APmrDKLmT(hFv~!Sc?PStCy9L|l)@7w)Vj|svY zk1J>t#PmFVV>B7BQo{%sZDA5JPNyo#AdWdacCwJ5^GDwP#lQUFbI<7jP6VM+vXtdu zR}}6WD(%}oWNLMpP|_q1V|qH1tc>JDQzivfD9#_84B?7_5aD<>lb7>?Br1{|fJtH7 z+dpt~zl22m6w8CLRiB+=q9j26C=Bpgit3k~Mc^Qxv)_<&P!wp#{z7cG+=` z7u7>Ik4hmXnT}TzuoG-Bbh^wIkJI~1o0G3sP;B>zD7)|g?s9~gO$iIE~$ypv3PF`<^?i2HgU4ea7rXXiK37w_IFmlw?aQGY84Y<+c2AMl$8s)rVEjSTN6ZycVDUiZ+yHGv z<0pRa+uwiT)r$>N%M$!Vz87?FiynRU6{)$EjH}a?IdX!TkF^d}cxk5hxL*s8n8n zqshhjScXAsiB#X{*+q#3s2>T33zMgh@9t+6o=MA;rf1sQwU7+r=T1UPj!oVFk$dil zM}nkghR{m?K$}1rzdI#~L{#lfYT_VjyPEr*y5ne-(u3!vR-`5-W!e6%O264-p=xoeYFd|Kayuc;VG@qgWK>X16}x zGzp3n;v`508Qg#Okv16#V>}lSR;S|yV=-H7=$wdpC?%IIp|>)U2>pwsCn02qomR z!Gc+LK<%~-vr&~n5)MeCF{>-vPToEzs0?CGb&YyVfykk)@TC7MSMcQdf81Cjz7|jV z-}46WwGysy(l4 zt^X>J@6E<1KmDgy@Z<*l&H8`(Lzngc&(^!6k)Rx>Nm1qVdZR6~F@LOxf=rKzgSXy@ z2iyTiz{{Nc+;?BS_{vg>Rr4(=h{$xjH=oF)tD~bkx9z&)_}rvgpFJ>QA_R_x*yYJn z+q-Je<@4iwezAGOvgY*IqFP#2Q~A|4lQU5WqqV-`)Ex(tXuH)eBtlwJH5%E*1QJUC3vY7G(>^4M=lAO7>UysD|iJ{vThv>$^jzG!`kJ5UAl#()q zMjNxkO`NPoU7?UMdH75l^aeIhNILS_KY#US7k9R(a3JIp5<@9ot2GO%GMX)vXQuWa zxTUlA&`pav$ws}l=+xrL%C_O4FX(Uux%##{&lchh12I)0Mi*D^swjmBMCas;*}nOA z_g7kt63hCqlqpm3{Z$!)d?7qPdhg8YY)+O7y)8w_*H?BjU?~>iFp%puKU-92XJ%|= zAYTYdEd(YRF673N5m6!BK3F*X&Z&So1^MZiCKiq!(?KGSgDJe_OMm*(OD`Kd3VCcS z2%yh*QbyXKF**WbKT_|VpL^%6Z<%OmxRBZa084+;%>DNry0MefvNFcU z<j~Q3-e5R3Ft}d;q3cv7MOWikwNFcp#c@AKnGHp7jDRBYt;Z-NkEfbiULnf6mr>a3 z3Q)Iv{(CRK^n4Vo{27rb8&Yz`LaC^+rhrF+5H3u7^p8Gzs-DsnBSr{=n$DHm=~9{J zlg0AlsoTeEs%T0sbReC`@0eOFlw!I7`TSUH;sam)gDwF^T*l0ZF@ufu-PEeAWMW)$ zp_K&)Oiv{!qDYGJ#Jmn!As+$?$ILOz74(NgVz7Lu$t>=vmUw_6L9o$H%PLqP)=!r` zQBrLVnUaDQryHan2}f`F;&*;^=~W8h8`Y!$AP@$jg9leCl9U%BZUhzfKKA%S<1ET6 zEJxKfT{WiWZalZ#m>n4Et&Egpnxz|Z*x43bp+PP;d|bnedKY#~=C zVFh?b>){U#;FKsaAO>a@Tm6S0y>q}25<)Cd03ZNBJd9@$Uc-fW-jI#POhQVGPvq&* zyADWH1SxBYQalENScg`TVwMqE5Ef0J7*Y91q^uw^GFp+Wd|3)44p7@4J)Td3v|JP# zK&k4{ITFe2T^C9m~<_!gN|^0j1ZWMp-+J33xABtncJMJbgwQWP11SQ5mq zrE+>K%SffA@pMrON0I}($3SwLstOr1rd8wh>3laWKo~`mne!_JIvOgJf<9kq-%20I zkffq;r1y^V+e|QJ#9+P=kMhHZ%Bkkgk$OKHVZs4k#Gz<=Agvh22px37;i5kH)`iyi z!(Vyk$1h$wcIT1tieAx~xQs$kB%l`y^~T_Uj-`1WW#{g>XL+ zq=iaD61jyz93nxYxxuHpT|))4U;x*_jLXlI8>G=eXPze#&4J6TMB$FT; zkzuNee&8tOCC=?hBVw}=HDMG3vy6}4+&cTkAN=^a=h{1JAWOcLlt3PC5RNKxVgI>X z+qL%M00(k?;O@b1eD!=or;O5mDG2$3n8f%1n)gs@MoP@?AEUDp9OL3<_4W_kd+W9e zY!`46!;3Sg_faN29NsT=;`0N=ZUX9^X#RUD9}1v z*>v8Z+(8b3U4?hnd2Pm%j^DkEN8SvcOk733^G_SPT#F|Yfc~3}PsaY@3ZC4czZp-(KK2Ii zS7W&z|0+ z%OY_Bm}@hhJOkwGfa6x}`P;4aKLg}@vv~45Ke!4{uG8PF|95`>_2J1;g`x>%=aD@( zmqSpZljO_O%`8tfdKI#w1i%8NteU2(c^zS;lBhDZg_GmD3#_X6{PgT8B9w_1D;$Pk z15*vrq|gwg(*g;Sm+{8*P=9kgK!`eFciRH}cYoo_Pk;A?i?6^!NN5*SL5%v8mN6;;O}55ve4h5g4HCR5rwos`6)64DpjU`0I=cKHd_XchB3$mF5KDzy8F)keQs zGW`LUpPJiNC}6Re)9Lb?sW>euQNU3$3k*zWvSWo@Y#A&#gtg zAtO?)m*En;FgcP{S)OT+)*Gq-Mj%`Q(*T- z6sBWhtf836?o`ejYtM2-Y)oa6Ie8YWh({Sb-stPnBpyUmRn(GG=f=<&JAJwkqoWi# zG&y(+h;oCyH5%0#Zh-aGU^;F}V@I=5cBs@1IbA*&-@j`%Q)44GFRZ0aiZC^hL~9s@ z@pNwo-M%O2@^;!~oblVdE{Dzg=+i%b?&6C9pHx3_|G9Vk&U+3W8HuYo6AJj8M9dsn znrW#Kvyl@L$=Jy1Sen72VOlF28igjBAl*sm3@4y$wRib}Q`_XE0y6Gxwpfe``w0+k z4|)N9Y3ytmP<&a5^>rk<*liqIP~04+rqo2Sn*hljJLmhQd{;*NxotNT)^cS1RAJ>{ zlXW|_m1P55smB${P~ww%@3!vv1DTLBG=6+~26Z~UV2pg{x4w7jXRrDoZvM>O=iYbw zVq-*3K#<=bg2Q0b%Jv(N6@@^olUEcqqtDzm8N*dgR*W*oL)o#4L7T%<3AA@=@9#YR zw)@psA7o|nIsL5W16jfmpBwVydilmCfte&XG7jcBFrHrRMP)$%VH&keNA2zdi{l8$ z$bdokqjkDy?dfs2k;Vg$_93oZyPTwT5amncYN>cf&D{33Y91VHy5IJ&S|H0!66+uP_l=RqwB(Emt58wCUMF|yTi9}MXL%knfBG^S4-DXD`_A9L?f%C`OT(=7{M|%$fZ`~SsUE@6NKBYpY|%IY&lW@palvqV zBrUQm6reR#gre}V{^^9*7Vz3oHFvzbmcVEWO1FOTTs~Sb1hDK6OR&*5ge6jD@saz| zj4YxyPqHmBW2a6UYW<$CfA8Xpml8BZWmC(mLwOtrIe<_-3o|mF-L+%(9q$Iw$8boB z8I8fEx4iS{Sh)%D`e0*vutid8nS!&cs?dMSsmZbWwzkTH)LxLhJ-f5rEC<1+3&bd! z5H-`qD2=VEmTkh3{K~RmrkF@Z#7I@i-n_a{4%@tL8{A%9={6!(a-2IN9e8qYJRf5M z9ADI#YBif=0Z`kUD<=txwJvcw_kQ~E28a#+$q#<`>d!L*>19b1BuS(*nHbM>XC)Yl z=#6?|@ZdBZreY~sl#<=)xBtN%!#Wa0GPR-VWSQrxS&}hJWPM`!NKs8^hHF-iu}3_3 zBA0Fw97hx7sZo|HkEvV?WOM`9aE8JlhoO;@BcVtnkjf4(4ENURu$8IntnQjDm;?s0 zze6Y`z3rpNWsD(tUSPO^oET5;dSu?(AOwcbwT)uZ_IEuvLE$%l;rq|N`f8Gg+?Xmj z!Sr235+D zumplbd@&mnB*CXA*#I1Fzx$I}PGH3>*u_apdg1ws-M2BhGr&d`yWTKU|U zzJ2Lems<525|G1COi>1Q&Q?H}Rpz~Zsf0xMWNER82RbDj&46}r=*jK0pW@PSUTY2y zLKL6hws(Jy!HKY_%-{dO2#WsvUYqgcAmC+{H-jf1y^8+RzXeY{x~_{Q!)92i#-T-ze+!*W#1E_v;vv7<~AAADAJ6HLhSN%B4U5r$b;M z10kybsmQ4ae!B2>Y=00>*0c7tjaOTIHez7F_q)t=w7 zAL$H`@6F;#$BS3t$#wdh^>=*vvi|>Bd{U_#S}cKn!eR=e3F&X8hel zwH5>8bYU)zN{GJm#PHtUL`AN4X66sff{hfmkPy)(-~Gs$dowTrk^qN3^388O`{U!>$#2B#AZ2I`}#7defno+Gakz@p+f1x9CTE4q@&Wt3~(T7eC2p*>oAw(oGuypYL z+m@#DV5@-JPagQtH~;+7#g}13t&9xUctndfaS$3G4N2L?@K|Yf zbWlf;g3-5_ig#N>OE>PA9Ip*mG62QW%+Wimaxs(DGr8`l5%4%EDu4_gnYwvA-2|aa zjx%Sg(Yh$U1H-Ada?tSRS6)@<5Aq9!n&MzxiT;wWwkfTY;6n4kgehBjFV4F99 z;*ePAH@%o27J3K!#?ma}@q;u6t&+-B3WUBcrrAHNKEjXrVoI@a|?t)bY@vr`}yJBor>gs!hf3 z<9ivBN!BXrpqUX=y;2(7voc z&oALTn5X#Yj-E~o^_8**_f0jkV-RLG&76>07;0v8uF&pH^j3!_G7>{dL(Q8Xy}3>M z-Q9aXcYl@_36?U|XoL!rsG_s=$wXp!uGks6WpT%@=0FjkBe3B@Dh)h%>4ReeNQ(%7 zjB%OiZWH+!nu^#EQ;Y^EIV_(3%J*OT#jmOvFnNUOY_c)hsAV9c)EyY=o7TiKO&9wW~U(WJ;Z^q{oF;zL6Jm3FF{G zwrB)3BFv?_pZJ{)fm3|p?A;06#Cd{daIKgSjdBi)CGx5HJ9g$rZ&_a1SMRI!B9P3u zu=L`+&;H&F0`kqX1qe=a#_^61@#ZL3Knz92EJ3GFfBE|_zVvER)xeHuCXpPRS&C7h z2PSEC;c&M<1-Wr99AT(ZrD2SZ;S?-79VCT7bnE=>M~k4}H>XGEO6mA)rd4Xqw=;%O znaRglkyd->CyS+SV_&`3z`cPW$RW=kx_8Dz85~M0LxaUeAH&lAsF|(RW|Fe(jg6l@ z@n9oq6|mL^qeT_PINiAEOF#Pi zU%WghXe{IbFIdR;&ky!BOafxEbBCs~3LCXW@Bk67nB}BYF(ob*gfx&X5jXCCxUXz* ztlFAd>{X10QWz@rH)uv}jx}(WMX1W`D+M1E!R=n_c4!3zb$i1C_nHlAoqkJOglH6IQ{;!LJiZB)@{f~G-J zwgvAL|{ zz1`sWW;|JW=?b3QpuZVURsQG=;K?ul8su{LSdtL9+IVI7%(%$QVB-X9U^Y@JD(R(+ z1`@?_dE~}P!W#}oF?cYMSRQK(^r9Em<+J3piiIa10rGXgcdPdN!`Avg0_1zMc=BI= zc@>^qr@vYMfBlo!hbJ+m*`DntWrG1P83|*$JUG2;TsK&L>7>H5Mu27`g>6KLCBOy> zFl!aP5fgKf2fqkbpnhhgdW%&aL3k=LzPQ{0?-vmQgdU3*w++N$BFND~T%~BOcO|8h z2oWgXxi?)peQ&LjBvAxvPjFPN&XQ`Twtepi&-IEdhlXt)O6yw~PZUf+)9SM^A0Cs! z?D%wDXW3+F?igsBjtIv=3dX6*uH9f0AfsjqOS8F{6!L)F$${--`*-dr`XW(&`w~Tq z5``jaCg4UX0PaB0Aci=_JTKh(hd+7s(l2`p`NBXWCgMIQ(_55KhRY#vuB0X=`|>Of z_S9%e2CCMFMsh6`Ea5@9!5k4=-jQo&Wt`3`!Dz}rBC*`?N+Xxi7qh7t=5m6;B%aYY zjiTdyJ9p;jXpts4wUe*q3qon`KoY!&0!6`FOjwzs_Rb78z_t}Eli4<-F)#!R#?8A1 zW>b|i%7-y2SB|Ak1-!BZ%#xkpH5;H;isbrm5hdlfeig*J{-#zkJ9`dIrTliko?@YR zu2EH_7>CJYJNgTfgoCZ}6f?6Qy!UBViE;$5m2?VD9crf3YDq>zE-jJ5P_QZIhH|Xa zxOFTJ_Vfd=sV9sW#4)LzXU-o5E8t~ORGHlL&XG2oj_V!69!V52Dw2$=$$cP%!-6Q2 znP0f6FBx*vm4lD(toAZAOd3d()KAunq+qIRo1B7CfG`|v>r@7EglhMG{f969>hDP` zF>!KYs*iP0X^mpcp@nKiiXxLUy~EwWQ^bs+ zf^^z!xmF)a0{`&3%-r^)C4~-VZhq&YmrRr;oBmVI(Mv- z43c9{{K;QkeExYTsx^mZmJX~kJV?7`1*4FQtEjo}-q}&1a;Pidw!~0Q)%yBknRd6+ zGH6zi6kfaGfgNgsgMz8?_WlMYu_B^r{d>IH$-}3I!9r}K+iDpq zg{L$bq^YKprP-Pa(zFyhE#O8jHCgG)6Fije%1$cYKCq)&NuqS3mIj{bE;Y_QFw}{2 zR*(o3u<1!w22a;9WA4yL{@Zt7y7)5^q~_I!mmhwtMbH>QibPPXm05GRTbBEks;r?JrI^c)+}}|Rkw|r$6U)3thY>xVpBB8E}bG%^NzbizPk7wOMy!`&BfAXV?FV7MHmO>fRGNEM>!}Q9h(fmS;ND}E0q{B+J=vMbCp{n!G{edg$Q2hv(&h9BxuyoTB8h6Fnu?baF_DlL`WQ7Wq$c;@)LV~X;FUjY zrCVIxTc56H^mM*dmSRe-Qq5=dSfW-2Az`*K(kSm-TpiCYZZn$I?y=dd`TueD9$<2n zXPT&6>Kv;p=X7G7eCkxrIdxTasLna7)oQ8L$~ggwhzufwZIZzTOdK2A1P5%712$$% zGEso-@y^}7&)qxsu6OUpP!b722~qq^MH3b z^YEPuN1lA?&9~lbXfnEDj6!8p@u{c@6zuJ73}eK64wuL8j)*yiP#av!<2Dong?0CG zw};cKnp(ULvy0v~HyO@~6i|3M3AfWeFQr5YI}-sKRea~p%|#xg@xG1apgT~GIgG$g zl4F#@;qqxanaMkEziceYIVg*@v%M9=Kz7%&Y2%EjsSgeme7be(R45mUuMZ8cgMONp7=y@~6lN;fs9X0F-_QR!Ngot^fLYiTBU^H+Br{?pQ{mWL@tddkHB12tW&)&%yluIJOXo7uP=fpfAX2}kGy+n zJoS@L_7eMemP`EQui(kWKYRvHelq^Wcxv$ne+Qm?@y}>`o5q`3ykh&11$b1zA2cUQ zvC3$6C=`fBNegOqXpvOj&nYe0U^u~$YL&7@i&?t%-VIBy&NK~o^{V2@$6J)}hz{Ad7_56(s z8~j!*5bttkMJO$`C~*oU!=8fOU=b7zZB#BgeEmR-p+c%sN)J;I4Y_SB$ z#=6$Xl~=EHdnG&=8)D?Wm(kUqG$p}kRYMQlJ%Dx!yKel-4_|rby^r9VfuN1bW|_A< zx?}4=3~L85F`Re9Ub?S;p(hfF$7zGwiAtF$g_*7XLGSDs%bIOAJ7>0!ZVi|_JCsJY zw6l<)P^;DM7Q0#Zu_ykauQ$&#Oe__zB-{$Qrdg)K`E)E09-Yn@p;sugDU92$8Fg?L zIcC-1K^|;3Qz5I4}v;;Z=Gw$X3t{W{Rb9oH`mD(;} zz~6WKK!A~K7-cXSiD=jGEw@g>H)MTnqu!Xh{#;orQ_IW=0te!fLZ*QJy2C-!V*_~> zX_az)OEb$(gOP@s&h(K1koB4dma;{hhqu4IqkZn--@Nno`H_l4Rn`7T|J4l0C3zS^M z;fonNo0M$~21QjaHQDW!+4#-3ZyP4elvTm--rh5DtG3g->Bs`!ikf8LNwxUUOYgq> z5f7a=L}c@E7R4zHWh8et6{DSz-u_(9X%`(~i!F8R_8mUp%sONJgijpbH{r%pedAF- zLm4#1x&DFig$?lQO3}$;ap8fRihd93^tc#m=}464+}X>rd^`@77}l3H@%udjj+v$M zc*4}&VGrK%y~8e--|lAQrVg!#2Hz#h3?94wo+EoV5;Dd17{7TcO`}SAOLOR!8wI&s z7xy`#P7i9RR>!U0| z3QF+N490Ny2@EcV{iW{hQ+|^}B#o}+)0M<*3|f}nl^bq2IGc4jqAIG#i8|7YzzZRn zbl!qdv)uKuF3M#)Y;_|x}4dPm)4AhdkX&LgvK1$Y!@LY~r8 zCE*k7Y)~gd`gj>gP_`zRFpmo9@euFyis6D77)$3i&3F*OWsM#FO1~yQCnCY@z&CDL z2oszsNSC6l5f4reQ+9`v*+3z9qB{}|#*G+nP%~MZRrJJpmrcu1+kWuWoW-EhDOK{$ zswHD%dxw?|ln@OU4G&E9R5s){2fUI}qoZPC{;uH+)6vjE#1m7_<`z@=zVH9x!{4_z z%H$ofz56%xlS(Buv^!>TOzfK<%tyo^V?u{AoWIyV5VM;?V^f><_&AH5)+q@pJ>WJ< z@L>@>i&tKL?GTZolEw1y{)fAHy;Twud21?2O`q%IIFliM+{20KTxqW4q-ca7B_D0~ zQchp5Q=ua0weNp(o&{EdvH_%k?J}FDSxbj|cc2FE6z-t9uiQ{fdUAm5ovfXH_jPa#L?gS7kMr>OJM=UTE~`UZ%1gtHpN6`pjc^16ku+dr z_lf-*{bq1NYim>KvTFsS%IcXr-8ax5T)WMi_2#G2EH^*JXz+9hv&koO;@qJgH_svd zE-t}DyLtz@S*ZV!&hlW~?c)lGfuN<$geUI)*4bo38#t*Mbxj5w*I{5npdX2$sRhg1#Fm5Ov@4uMJ7gNO ziSlpk>RFC>a72xpoKCxe1L96@a&^!fWeng?DXZ<^=7w}?EOGgxm-koyJN359auQjBA}0~5t?D3xT)2p&b5uHBPDAu&D>^;zX=+CAEQsrqE% z|NInB#{Sm@4D$JYcjD)N2cCSXM)<#{FO4UcV6H0G`S|{1u8Z;H63kV-z6hRt;9zCQWHCwr-`;gZ*1!IRwo^(mf=eKP*Vc#8YwU)CRk!K?SbyFMBJ<9_#V|BUus z`M_?Cv9+$Ocf4Ey6f!Ht_^KrF12^D{?>%)lP2gY&Qzxf=$q z&NK~o^{V2@L$JOW{JDC6>HoO&{tv(YbI zzx?j`cI$@atVvIY%W+pK9mGf#VX>J4Wl#&I6I^+|5F}m6l~X-QPZ0C)jGwWpts0aU z%)H4}x#^kXK9Y`ac&ExzyL*cPR%>%7 zhUfaWx_K6?xFp1N(@Xeb`^Q#v{AM=0NTZ!yWH zmc29)@`OuXuZ?;PFR(_RbscbXm+1!bsSqieJc<%hRJ> zVH~%D^^Td4`c_WvS?fwBpkP5MP&Y4TqFrGDZ-*XnC$s6;;e4jjy?1Z0S3>8mD*I@S zjRR?b8AZJ1qen+eqSE0ihq|UFVqV^Pe9ED-3R3*QOi>I6+7ty|+tl3Fu2R%CgwFlq zYo|)R6LhCc(bTCmQ@)rKa#YTSNr0wnxsu0%4_>j5W#x^v>x;XxJa~G`QY!=GjwgQb z>Z>32o9o*2Hr#142g5|Zk{lehJHkd;v&qf|hL2vqf9Gh5B@G>Bjn#{DMj)1}frQ(P ziQL4lLD8l!4hJwlp(j?a-WXM3Jh0g_>1bDJ&%w>xbO>h&59bFbx9C6XiRwuWZ#xo*(Zw=e$s!yg~$4H+zYcr@0xYs}oxnlO4c2+)x0Dr&^|rtzKI zE75knrW2i>jEAbS>t-=L^XN~1_ug;2tu1n#Ra#|gyI{d+77sHnT4n208id)|ohJ^g zC47|sy_#^_WNn>b-w4$QC42X~A3QJvosf@NXPu4qV5Bf` z=KdLI5z^!gYeId;Zo51erTo2;*}FVFK1Vdw)@WRlkN^BP=if>IgTTV+P;G0wNiUPz z&Ec^O2R_~kKEMCqu_1q8n637^)wCHj$P|P665eobF)_F&BzQ^A?C|2OaX5g}O0*q!7EHQ8fHz!{G4o!uP95jRElr;APv4cypTMN4B-ec$(f z`SLkoeRH*-xL&DiR#B;wx6IAQ1c_B5PNz+lKhX=GDZ)|;SBEd3*pfCvf`V%KJ3si~ z?ek?+-l@bb2I%h^G1~3|YDa}r6I@rNFIh<#%<`TX(}q%ZTnvC;i_;kiW&)|m_L;NO zoLpzMOE@B+gmx;Cy=q%;#qUoFN?jyAuE=W-R{85A81;pxQfxX^d`6IOcUerzR;{5kb!uza zp!E#%mNsbGv>txpjbFX`PEW~)`gsh~TLJ`~l+N%l)m|a zc(L>KOxnA^NO5l$#*3j&_QL^z)r**#=Vb{jEpM8h~^7>;=umfsjGUbkDo z6f$EEC;1TCOwi!BzI%IjreM@&=J)M>>FVB)(}}1IIMC2Z1_ugjtHlcq!3IMix%shE z9;E2iHW;AEU59%8LK{BfwjbPLs#CaHWvJ3_=o%_=9vdYV8XKD%JB;&>{`#$V-_mq+ zPoxMd8D{igeYH9FfbejR3Bs2XoPD56Mj3+d81Nm zlicraS>M(v9)IGcH(q;FXYlnefsQ#!=|MSdJ^Xnb8~YU zUZpl`O?28_ZS3 zIv?Mk%yluIw831}>xp?)qf>kMZP3e@26wVqDxsA&g#4C$xm(&dtRwSd4*0Yx-}jT%D$L`T za(P#Pb5l0G9y4O@lq(n%Xl18CZMQ|nGU;HrS>|xlm8+%(JZKMae%M}`4D^mCnE3K_ zbD1v8rWR7AL8sB+@QqH6l%kl9Il7oK6F9&$32eSYV_|c+5FrG;k&)UUQ$}wX=)dBp zk5cOtdLwF(cYv*&UEbMJTc=WW%zDsG*K`%rDo{(yo0`o@zC&*?v}yN$`?d2Q{SgGx zZF&_iMO@kbh1E@4W>Om5kGgU?Q#_iWDJw!-j9$^j_(X+~^R4tc@=QR3p$&}D%uO7h zb4PmJaHjTT43sx1kPgr0%clwqkO$mRFQYaYk%^Ilk0ipIce+FoI@2cLlUi$AB{!$l z3N8jt?QC{O#Nnm<9`n(kpBoIh9V)p(Z2}wJW*b=EwwpRJ7rx_)lR3G<9jo@v^@BZ% z20Nb&Uh&i~-~Q+=47bS%w=)$TT;9BAu{@imZ5U9uC58zky`D_gj%t)rQbglEM9MFm zTW~rptd58)g=QJ!T{+mdc(@Rz^mY)fCL^?sqM4D+y#lJ#*+Vl)%nFsCY{+ldg?DWL zJ_2S^sU1$Iq_zoJm$ez4Da71XlRoO=FeofvK67B(=Cxg$`$^6pVQ|U?jp4Wp z@{bm_ae51yO^Ht0FJ%v$KD0XM!%&S@)X^O#fs33xFtjs}^+2W8&bS;71A;rsTemWT z4b>Wk79)reQwOM+Ntqe#%NW%*n@mmlLxa;vIzb!I@Q+vv>XQ zRK60gHYC*==(WdW?VSpRJ-Rc$En{q32URU|Q-gK+hQT~x4xf4E*Dt^I-c#Rs_Ws-U z_YY)PJ|p601G#M|A|!13kl(X;?_giWCAW|i@9vfO?MELxGdUY%K#493*bym2X7ZVH z3q84F+97fxcnh%%p=TzbWFJP2je6hMc8tKxJ~0v_D?b0eKF9(gWGcEhvKZqBq7eC^ zwNnAJOr=ukF)g``Y^oif`O#a~`qfrbTSvPL^zOXmvZq(e-OKUijH0&HC@>B}uJGLU z#7(vu{Z-HW`VCnBGvB=JP`}-WTM3dekUg`@Wk2F2TRI7$ykRr648Y=3;>!gAJAKWy zQ?YOo3U79c)7622^NF8~Eak?!1~8|CqwIPvOWDvsmuv5tjMJcI1JjF)5Dg%Rt9Jw! zhl^g)rZd^`WI8wio~j;SP~|chU22UTo+<+o!c?-pk^6rC;y7qZI&=n;#16PMY-Y!U z^B#FC98*lBy3AdIbGv}kTv-=Ntt za`5?=Ppq#ufGrXjbIv7#N@;963PKoN-XFj2q$kt0ZM7FSSrHFzkKhQ)c@qiJXY4d0 zR5~aaU54!Le3^IohZvfU=cJx%MqNC@x~_hDOE@GjcxaoO@>`8Dk#WX)dWvRL*Et;N ziFuf!yDw<5$uOeo1;4I7?+GQJc-&p9#o9DBtwD=Gj)S@EtgUIFcW(;Gt2CC{wod4+ zjPBZkY<==K@4fr(j#FR1@1A|R0x8-W;Gv5N9D-|=EiEbwkStA+bj0f*9p3rX5jStA zB#|?lIFCP*9W0itPLG;$;1Q>Q_+$MC3Tam^5_E}uS*MZMx!mo=9PG$fukT7a;V?HH zC({8g=ON0HKa4e@xE@Y-x%nwljX4q8MDSH^Q#(cwD0%RSs{~7DtA;{#>(;BggE+A8 zJNT(hT^yo=H=;$QHPkj?D>rtneB;-zyzxOMJhgWAk;^B29v5Z6D8`NWQLwWzu|gao zE^v3y<;#q(ot`iF&6sCAY61c|!WPOW76TIR=6IuFAlDVH%w%UX9;Ry=lAObFEFjC1MWDL;wX;z13^B$v?GunwYA1Qgd1EZ^FzBfS}i6yfNWSk5bab3D$>31 zK?4BMK*pE6`D-ng!jsO%hyRT~dF1>B4EA|E>3sk1 zz?1*AMi4%{G@k5+xh~+H3v*qJC%a*;>h(qN#yKR zy7}RM%K*JF{>6BT{^0MxlmGf>6b?huzEcOj@zPsw{ys*Tnf|lCdhEc`!bBg_)~4v> zdB8$GYo|_U<4Dv$)_?qacOBzJ3Af!1i?7Z!4R`gb;@vA@eKGiR_5RYIFTMXOVSQf| zPlgqr;>io+U%dbDzx-wY|5<$!op3qAg2+1k-8(n;6bbM|@vuA}DEH3gO9Re8=Gb67 zC6+jk$1&TzJ)R=ja_stJp$LIBf#ZRbY_}xOZSM|YrZCEly8;2kyPUP?)iRcAsFo?( zN#F@O!=VRX`~HBz=Ff9LC-U9;0Pyvx!bYhXB|7aac-DYX50o@)r=I%B8*je*Cve}f zk;3UmU%3B&>4)$Fo{hDRd%z5bLg2Z*I#wVfi9kR07a@VS{V>oEbNV?rko6d zXG39No3qd+FO3}^65QjV{)9thY2LBY-M4d`Wx~PAQ#S?;Xp0*3ix#CxKC-m{)go)e zK>NKM;;=Yq4$;WWRN4U18;lSBfjhG4ryt!Baz;X2j3B*Zcbp8h>-cOM0g863QlY4; zt9GCpO(UQHeD9<48Y>)bRj!3=zxTs)1+&c9+0sOeN{ORK-5~ZeOQzJ+!{_#1E_z)k zE5s_rXvBtCLTStrpMm-LAmJc!Xy+%lCa+(#V~j7?8wvV+tk^f>-hNYord<4OzdYm? zCDLjPVhVF(Xzxm2x#%k+hG2I{q9MCN?`2H(Dc$et$rwpjm28)Alsx(heHq)y#DIIv;XA}fBN795>Z3rN-Ol<@V#H3$fcYph`3S7 zT^aQNP;jtjcmC#E_a2=OM4{5_A+mW8^AH$30;cWhP)LjoBpp5hb@o4>m|C>jieV`!pmg5=27{jc29Q;ej1o(PBXI&aF_XzNYD@}c+N zu9AWGen*S^iSr-5|GVEqZyEzi32x69W=78}IhJm@_qIb@7gBD4B-7(zE-Uy$U9|t& z1qR_ncXG}V95tyd4pWQT;!||A;=Q40|L#5?CC#0>DM%0!GvwsCREToMaAWe0Z9d@c z;^TK;UGaeUU_jKHA-vS)q*!5U^@>prSZ%TvleFzC56z^ZMw|0&y6%XpDkf*1{K>hI zd`u|^%Wtg#ce(705&xKZvA(UXZGDro7$&~)^4sse^@a$dFk_p_Ik>A!Ao}jyl$yQo ziHDY^mcvOO8JV8%4w&7cq5kNO+owD%0oK@qZYK*yrfM8hnkl6np^3i5rLJ_(V0mOM zP1wx_c+^9DmuQjOK}mDxA+SVn#wQ=Y)=63E-eksFzrIoE8KnuaG?FvgB4wwz@$Mx~ zK+)jr$`!}wdM2je<*iwlz3*(8LZE33dOg7f7Yp!oI&fRj&zi$$78>G9|R9bvR0X5jkuB}h-aSS*AKiyoG2UMHtLl^&N> ztyRmB(5WwPVOyG2uFbdh#PfQqUW%vu{)y!Hdx=n;13ub(d2Ak3`3RjxG}^k7lY*to9AKw?za z(FwM$!w&0z_s#dxU9%esh|FrGbXJ$&lO4U|kfc+i>Dyko_3%x{oh)bK8PF&h`0m7X z260&V!t|U^AWYCX*N~*SZhf=erT9FU)l@p1kQ3Jo4A(f&cwu9Cb0C`oU+$k9}}yJoST5_EKHLC9l7NC$Cq122Xx6 z{>6Ce`v3ZO;K_zRqmhgU_`qx_7Zfp*M@?qG`oMT0Y&Z8zm|KXEQ7(a75tzwiJ3+wi2; z2C-PRO(31KYP5sfvyem3y!H?O`1U*Rx;>M#8N1qo=1Dyn1(TZcU`hgx{`A*ge)5Vz zFE4Satlu4HJ!@mz)&yFP3g-C=jthFVwNtL66wR%Oi_M3vl1a$svltof^3u`nd>U*++UK3#VqB zRwxpM@4Zg4OU8zFxdQ&KR!@S#xB|=ro5QhcFm(OOQ!jn^(eI9hG~JcC9+vN|*g6=y zkwcMu3e)k$xm)kKE**4=#eoEgU?w~73QU1p6k$|RJI75tg`29`7R}HiA=}$)TU|b{ z+rK|k?sbVtBNGfb6LHdSp-6>wc>93ZtR=9mKR7YD1t6f=0x94k<>=|^&)Dit3=^N$~&DHKyE#6U$^sL+QZLq8R z@SE4hZ0W`0lb)e5R)^X}UT2HvOp2R)D@wqfg9%|PG{Ny!e7i4_PPuX31{PrHy_3|(4o8Pk;NEgu^BoL z29hPP^8DtNo*}P=$deX@EsbLm(4(4~t>(4EqD!aJwRWuCaU5*`zs}lDsZc(6Y>NQl z=&%9h$FU`<<1ha9gV)c0`x{rasEM8XSGLXfN+uJEfXqPb?(?%2%<7pxzS!+|^H#kT zvvCN9y8ucz=@Do5R1UQUJoXMg7j+mlme$$^I+>wJbmx>aB6@hy2V(A&*NIU9i4dfF z?&>X`l<4eU6am_CdV>dZ*+FhB%ISE>Y|y$_ zav}pl0m?BnCFMPouiMF?*zWILerPkLC-YZr**y~WSXg7A#AGV{-97MwPAZ8+a$`y` z=S< z!sBy}`1D*UC3pGC`nQ=0cQdC)a};|Pvvap@Ttqj3o$=LkBl6U zBGE2`4y#g~NBKvfUk z+zJKvx;DD!)OTKb|J}FA_}-uV^1+w}aiSQ+AIHY`jg1T@aSqHN+*>ME*W|T()^6<0 z*xY5Vm~i=LJz}3^ByGav7~=%DGu{cJbgefgX$giE(QtC`R2lTsq2(PbD}Ig|URfCQ zcyr~ijM!qh5@Bb?3&p`G#xLwn(LA-DhG@gl@eNmlIZ)81Va%8TxZAW=yyI3kf>~LW zrEl`yr*BxKIA}V+PeDVwt_4KZkfXM)$>u!#+^>KCFYkH=j(+opuiO$*n%RKPH8r;5 z_*kMW61WYe`98@(Om01LaDOk`Gf+XS3Mbt&mMla_i-GYR&Ih6qL_qAg+3v7OBom1w zhAJn{of<6l&+WZ>B7u5&qB{u=BWBuu>glD77gec{O*4te(%CC^r=51YN?%4Hd?GZI z6usGm6Sy}fcR0f7?TLNINAsKSxH&8Fx|zGanr?wL*SFNG+=E5Qrhp6^lxwS%AN%PW z?|tz8;I6ChzWZCx%|f~kWuyC7jvk!O=Xq$WSZ#Wn&B5j>(Xm}828L%7h+b~QVzGhw z-k=|&(3u&;z2wvyO*DbRCl;6TU2c}jWqYy<3%eeg0Y5P=78soa^L;HNLZ(B z(&xLo%4eUswm=%Jpqg^FXz>oq!ERe>X)zlkLu|l8um+7`teELJ_2MgcrIZbp)q7S{ z@YpvrHtV~$EyV~!XM?N*Of_0|Klz)tKm5o|=1v^lc>ir?YZcPw8DPksm(p^7D|_jA?@807PK z@0wx)d)42OXJCU%cpqf!dw^Q$@>5C7d-Ms@MQS2;|DM7<@5E)FpOW_OAPjV z$?LD+$sbxj#gor{GXBMQ>W6>+%lcz5c=i5w*C*qDjJ-8~Mzf%W)j1d;Rhe?7J<*`P zgDD9%lSpGA%Y?9YqZJL)f>q%V#*dEe+UxE1Iyan}DcJ9ZfvYo3!(F|q^~oCBr>}2} zzV!ZU{^u9RlU?jW^D#Epl{x<49rG<|z+?l3H7S<8+t9>GLM?SxtjRYevL@+1aaqbb3ro z)0|7?kVZ27BZ-_tr;Cn|Hir*B(gJ*Kw19(-6^8&b$HAM+i$!lC(6cfhacNCbp*Pyu z(7L`^?DhwHeQcueu{EEoKa=5y(*Zn%SXt4g7B>t-R$2F!Gs}bO8daKbbz}V89XsTp z3a?BlnrhdXcv-Wm0m>M)@@AvgGj{d47k>4J^B*YMWLB~#n@Hb%dESf>GIJ`^mzCTI zGYq0~5mj0J!EmY^zw-I(qva4Mf}I2dh%?NFP|9;~mM2l3iFip_JMRWrF7H9r;k^^l zo^ZrZ$0DrJrp8JOg*2y@LmD<4?q3N2&*I~}HT&w`-oZ^l2CioxU7-_6&o}EaKbQ2K$0%*FS zp{>!F+<)WYN51>p4?p@4Gpd>thU7qRI5ik^Voe=Z)9_{vFNSa`PS8+*80`vrL5q^T z?VD%ksGeTGgiBt=V{nv*J^oGG3d>z+VIhHOlxm8`IH%SeD+-VxiM!krIjvbkxneQI zi~#u%d{{kG)5`@X*3r?}L}yCU*zRM~MOPLJ-&MLeQClwz_#GH30~K@ zTsw2mehA_#-ud9I8#A5tRo8CF+OGGGj1T8;{O&7|c!`3&X067`nj{HzmT+ZM2GGQT<=$b~smqs+?Fk-6yr z>vA(%g%QpQ@tDaDzw3-02Z5?W>9jUEOl`nN(YG~#;W6&8y0&i}3X`Vz>QpIbo7{Kp zku6eWZ!k~r-sU#aT{bbq$kw*W347-J-C|cXe{^OojW&e+dfiC_t9XWp@q6|b4{JPnaFVR;FdT| z5QGa1HEU!854?UaYakTs8yfk(flYpWN4;+KAAbJIyMO#ILC(CsuBO$XQ6uqiW&G@s z;mTBkN9ftS)1sw$8*g$*689KKYwh z-+AYQE>tV4U)KUq5N9lO!8o`UMR9u?UIc?y*n8WtqZyJ&U-j*K`e{V3 zvxHJ9LJD*RS#u#36miz&U)Z)#np&KoSVAQ2VV4D{wAQwDwT)`Ke(J=O4rZ=zZmEZ( zUjIf$VK8g`vDh_ZHg3n>?Rj&j+@=I6c&DLVV`xVZwN1Tn&Bho+251x>_~a-Oo{i&!?-`)h|9<|o%G^5!a$0poX3^CKOekNTU&--Y2 zYooKyv3*gul(_mUr&oBLULm&yc&{II+RgC^uqO`T zT(E=jK^TC0m^5DsPd)?~;R2rg(tm&_9|DZN+mI{tBLajQ$Ls{AB!#@zi7Wf5B5R7`J-= zyX&)flK3+^ViQK@EcUaveoS6iUu!8jWthWAQ-ahPR@#aREjqSx57v}8q=4N8SK)mEbD3Dx> zsFjK4yrDp$%cVuk8ln64rw6M(TX5Ait#2_K^nTEGGboL0zu%{}j1EbmaM7jJ=yYv} zlW8+Iwvr|X*mrOC@Gh;2kL}wB@w^F1JNCWpRr1Ta^-X$XlS1Cs|E(YW`knXAGci)I znF}1Qf}culyH3^Yp!68Uk4+a-X)fg=anx?-Sg&_rdQ4DbwkuD%+ai8#XPePh^0QV@ z(hct36>(XeIwOx#6yiYz-s}r7sZ5Y_(~i`Md!IQy#eBT{+J^Ra)@f4DUcI8^JQgf_ zWV=_0L(Oz8~>1;&&Xo=gZTGtCpAO$ICAn_qnJ>fxm0Q^5aRTa@wlI|3U-T)4F@2w(RKEly(UCq zq-C8`-@k^bg|rxJm4Q&#X+8a&H-7u!hfuL)x{ve=c2H%ssXMi%X5M4BTG7qRv95hL zZ{T${kE2Z+5P3E;5*v@k`JkI@g7jQt2SszGWTZz7_ibG66%hl<0aiuBE{s`Wb`OTCmQ;9Xi%ZHc4c>MX_y!ZZjO=C-QXMX#DFF~0U zP=PVDhK6t@ED$^wS5KZjQi(()4M{M4ixF2iHBvHDC=Q}qcqi>4E;n#^wOGm0m2!kL z7)gYtJ$skZ0fFtRI0O&UP{$7YsIsO1}O*XM1^6Z5?1`N~K5Y zl<6EZ*RN%OW0mzb`KXaCRBK&_-_ci$I80+4gi%2Ra+{`ib%C#62Obmk&;z$!@%>kS z`^P_kDxn7r@GWihK|+j!FuP_B4Mv>23$Z&w4#4m=>*_Qdop9KVcw(q*RzPva z;j{{&O%EgpKWIp4rB*`^L_G!*e`0rLXta_T(5s?hnt;TqNZ*zFC8L^DHZ?ZO+G}g- zUAwPe1j47WsTK0BxvRhb`pd7s*PXT3w%|rn_oh_D<1UIwt3s;-J5fj{cAMy2(Y?BF zt}7iTn6T&&L=%>C;owv?Ii#b}*zR&Tnai5wX0m)mpEt8aJ2YZ$Y^dyJoeqXQzK#0$ zoo)eJCI<(J%2OKybg{Dq4HC2+zxQC+1zF=pvq`5YZ=9p#>e&PHo4b3np2m)_wpMAi zs6vn2GX=C`K+WqL+QIpuF|y;_SN2;}s=E3by~T9*_uqfxt$%K6SGG2(6-rxdVXP+} zBS~=efuM&h6!j>&ubAkml>DA(nD;|09xiC?pfAJ>wk?l5b$EPuDakScoYCqW?#Uy? z^pKNu7=@6OkNJ31+VjfK!qxQyE=qI$YAJF<=#7&EVKbzuf}Q`TAG`Q zBre~$ow6BB90v!BtG@H{J0JeO5R?d;6^N)tyeE~91>9pL&TiDwsLQM|8RQC0u-rG6 z3V_IaFdY&sY8FJJi#I-h_uQVdrQQ20Sn(U)V-!G+G-t)#{Y4-n6Z=W9W_*fna)rQ4x%} z@&nYsm_MKk54Sgx;2O2Q)=?(8iPg+po@CWE>)WmF9JsRoAIPjBfR+BrSasS zV6GTolj{AYPv*K9PyXo>Jn}{GWXS#L__6SXy?nkt8IpZ*Jo%{SQ#^TL{EP9_qn&>T zp7j11rJ8ggB#jESH3&nd#`?2%aV;6lxJ{(scV-V=I{-%pEF1OPZMYPMfM}{@Ge`4; z>TY-mFUFG%-v5064e~FJC*{#k@#KHBf4Tl|!;>~b55W%=yICI|5;~1!#AU z=30g68ht*eeeA5N4rZiIZ5x(feMq2y8)h%XI>>Xg&7yE1E zSj--tpI$s$AZ-DUG?||q^hKQJxRlL1X-v|XLTZ|;sat1s6J&azD?NSJ>D`sAG_Y~k zR5&uTp&**-Iy{3%_xtg+MVE-pK6yP~yS^6uS8D4SkIg;Lr_rXmrdApX1~rNu-~G`! zzqY#mXI_8r_y1guj7}AM2fK@6E*>1(w0(6ow{UcMsgm_iPUR4>6k$lvJawv#7K|UC zy#4V94tHT_&Lv4K?YjEl!cZ7`+%)BNTHO)q*CJ9Sv{(BV9;&Td{U^1L^7)#C>wtyG4@j#^#&;y1Q3s|RpMDZBIghrF6~^48{7 z10i%nHoFeBX()qP59Z)4vrk=n=W1~MdfyF?{qnWnzlmZ(j7NME$9C*F5DJ!ZUE{oS z>4w!kI~R;Kac(zu;zhtH~9a_{J*_&w&aS zf`*_7N1y(|&OooTQ;DrTd(*VHNe&hytWq5pRBal_ugh>K<)~|3-+e`X`{6Ofy72X% zzx2ij|6;`{6XxkUeCH!~+)%3eMLnxWCo`?J zEez_)EN?70{e;n@Y}XCl`r;eEIMGT-c=gx2UNL_EopFb=P1{kvY;9(&%cE&E7*uLL z($V2iw(4A%!J=$xX!K4JfyKRBPTu~HZ~XRmZ+sXJL>=LY)w6ef`L<(+$3kXwa#Pf% z9z1=_!{Gkmpa3LHGLdAmgFNMrrbDC^Da~yd-nj%nWuzswGZH|WBq0ypJ*l7tf+Oh}l zdEu4>4x9>nbIc$53e0ujt)DlU{>m!+>^*gqB*Cf8F~gEzkJJoA4{Gl%tHU#u1aN+A zbx6vt_D88&g;mwmT8YTYNAG^_{7--Qn>XHlo$c9}+CAMpoKKDJ9Pv9^WkwdU>Fusk zcQoSm=m?9ObKq7^w6)c)YiLnu>_*%0+Lh0KYem9o7rs~y2WEEWy-+W=8MXL4Zr5<^JTx?9Y8t5(;vV5 z+B@$WflDx(mO&PvBUu^BiKJjqh9VMQ*glk!v=o+3&H^D!WVucVrZ?!cw1)2)i=X(; zA%Vmgkj@~)&f5kc5zM4A(dA9YzjoFuGI^FEMS_iuF?MNidgnD|p_oq)Y;?os?J3HF z>wvI>?7VBuH<;VB;JxS7hbAWWJ^bRY#(;OcE_>I$5?Tf0HEr9U`|Xo!s@g`5hOB@^ zZC$-aTi4dv>Zp9fAL<*(9(nTTul?>1e;6Mas^qAqRyZtq%VX(OkI#b7#qH|S?vWvo z{oq}`k=a~KiWn`L4!vDxM67f_?^wLyANI!?(j9hCSl8ugmf$+DSpRa`J@>#ew7gho zrnByB)Bs2LnengByXU>0V$AQgSO#5MWqoZ;W6#%)vc`c#d8TycC$HbXbHn~yo;ypl z)z$_cz9~n1OrUO`d+Zla?dsGalD%DSjhH$*6sEfM>P&3>o02DKZY_QDC$GP9{`}^J zk+KVGZH_D@DIx9+4G)E8&h&iul72yZ03%$$lh6J43!KBx$(_E-Vf`G!9TCwNB--j_kTaEulinok^1D@`A_lWh4C-m z|J&%_h9^DVtG+Tbuqhyl(5d2C2M|cu=)E^=-5-wj3(* z{?d`J{_ID0?kKR8d*@l+7d&}-UQ?|p*0cm>zWn4(e%_5&sf{v|PT8O|>S71-v2z zv@*Fd;SM4^bduNASW8PA239ZMmmS!<%V%1B^u+#KcJ6ucw%oN(@AGcF%u98`e*48%Drl)^<{`VhH@^)Ff1~YXu+dRNs&+zc;otJh{|SGcFoM>F{Lv%;H|Gy z*tUK9rC0y-zP52)Cunx<|C_V-4v(tb*M?8pOlC5@_gT|>@4feAl1cBqH$oCf0-=RY z=+e7_h+sh!K?OvL2%;!OiXcs}Z1?{5Ip;m^A7@|hcYV)rw{b0{N;Nrh=!3G8C*xHOX zdWFEAlqBSXtWS#y2#8o@gOQ`Bt19f(O*K^|6{V0V88cbpVW-p`)=PN;&z93`aqr?blh)w~fkL#`ml#;QM1sKDqEdI$g3V_xY^vRI?Zb7$^>YthJXo@P z|Hl0rHyxT2Y@I42Gw?*g3)?oNSTHz!`m(OU7bXk%K$f3DL?Uxh9K+lXuiU$PpG_bz z<$5$-8!E0WYe@yPB8J%{wiYy00@|FtqB^W)3Pq3(Lj_6MdWl@BfQZ2RNg**C?b>1k z!(U!v_f?naU_z%F3VGaaiIC`f`_$@)O{USf)!bl#+2WkFqe2FBWI?&Jz+?7zET0U? zxLl={uXp(Sw!A&1dB@KE8yD9uIeX>E%azN%*t7NO$tkt7s!e<%HrL^8>2{D=%<<3n z&avv*G$afZ?of1GCOW$K#O1HQyQi~9Ol%FB8Ex%suPaP8cKV#Hg? zdO{5f$ZKhi&0*%L}qxecR91a$PunAZs&)ABU$zo+TS)!6@t${cXxVb6;TD;aFtvUX%y?SQTs=4iT5C@f+1EZNcjKB4ga)4OO>mjE0)Z(w^3M zF)*C-I9F`!nPY^wEK_wsK&38kEi4CVe|n$A>bJVgiQ0a5YSsGTB6D)a{B0-BzH$1Sqw5b| zKC^Gtd#B!6JRYr|J^l5?cD)iPRETAtT$yJBVL^JkQmZ7In_OA!e6LKr?8uc{-~M2j zxxQAZ0&m<5aH3?9))~{wLIzJtKqQknLSQK`7#y$jig+TBf(vRHnV?sWBg)L~k`f(0 z3rAyDPxNSowf)sC4!w}TSBRBzACCg&m^$#;mcp1(%NFvKp80FaJhNYH^xEhwB%a8V zOJp){-&!Z9bo0S|Z=X8<*8aoi?tJ<2y9*X>czIj(_Q}O1?M=(q)@*pWHmL%-b#Bl5 zm&dueyciw?1F?~&jU(t>5{oEb_TiPg-~NRcuCLQ+()ROoBNVclTZSu($}2`3kO<2* z*jQ|SeN$tVP0VKz@PI~@{xD`_;(`B%%tA(xa+x*tOd(Tb^7q%P)xalKDN+Jc0I6JE zIQ-t(xkU^vq?RWwZ9Q`joI5}Y={pyeYRe8Cna-`= zYR%x6H@VS*g1V97>^xq&Btt4Id8r4OtmVRJ>u-E}{oy?cq)8MMil3Xe=mW>+1(SO6 z^RLgJzYiSWpT(0$OMk(W-k-;RcK$~-zYkAx(PW@l1`!P)RMA;oZ1Q>4GzEx$k$MvC z;{_lLnV0s3c=A|e5s@4|# zWkM5Ric}oD`EqT<0WmxVkj<@=k!k9xMgUaxfVmgnbmpyTt51KpWX_rQF5Ua`!kHPS z{!1?u>+REufjxHj1dqVTG1c(Wltyp_($;?95+if46>nX+aqIqLIUXgC_61xTAmS0p zNq}<*$2mQ9WvP6x((I?>(wV|braL)OR35l~=?#{FgagZJgULNHzsBs0Ui z4=y}$a?{caAB|4=^5)BLAKr58_$!BRZ7Xx6G;8OY^VD1C>B$)gyX(JLIT`rkmCI5l zb4RN9;N_cNe|_J>%ON_uYF!qIhDU|yWS~VrS!{*1@!(*aUttp2gd$Dd=GtgntrT)| z5y0#;m*SA|#3Ge%>7l9Re2C?j;32EqJZB1sxQOu}51VAwi1;i|$GfM$c&&*;Vnfmb zOXsA=YNn{CEvyZRflH=U+1FZMX)$*#TeKRdso zE4pdT$2$eNn7)HSIEF-v%KnSnlL3vKSmQE|tcg#ay!_+iM+I6K*6afL6~VAuE9C&8 zJD#Z(=*8Yh%aoQd@C*ePKvy=@lpj#CLH8?hR*lYUHaaXL1A(wTvi|V4 zKxNg675l%w|Ji{TkAHpR%bEQAPw(FOaK`Ae<=ej8Bgw=!uCQdm;8|2V@5$8%1l+a* zJ8Fj346Hcw|Z`Vs$ck;7fFnCDAI{k$l_Z0lPf^C2v&c(ELAhQ>g<;f?%ii&;F)Nw*w1C@qqPPH z0j^Jg>@_w)T^cVA>Zgu{RT3WXLU4N3RFTGIi+UvjA_-TJ9O# zd-2P&AHUUKb!5+}d8;m8|K{3fy9N)w`}WCks$p_(K5)WdrH>oto!7(z4{)Y;q`H0Y zG#pyH^!@X9?tTYsqv$vkw^+qcilRD~oaD{7Sma79pfXECdPl$)v;$lzu}`fB?pB?} z^#LA%3e^5+mZiNIxV@6xeewKiy;Q>F89Hj5s>)@(Ur^x#pIc5YWV1`ZO0EEczx%~R~|gNcl*?i&Bt$E-RYkD!pq0k zYbEZ^S~pl!r8yHY)$r`<^cO&ztfxB1AsA=gv0Jxp-jz{RB61#Cz=QKd2AR}s@DxR> z?Jfh8_-5e3*9~Q%LQa_IF+oO z{pP#xT)Tbyr@L#yrH5~Bl?D6W+TR{;2eB-mhZYv3?|e#01y)WgrJ$ z60fOdP$*nrTbZ4!akp1E!U~Jl?xxagmRM7}%%xK6M3AGRx)~UKEIe_ji~xfvw~fhx z2?#JkXVUZIB@v0j)juN?atZ`OdtFaKB4y~`fBNk4jq}$W-1NcX+Rbmw&Of+qYMfyY z%-FqINh4%tv8-J&sWk|)XduU{R~Of>`sgZfMti*n=%f$~Cd49eIRX`xD>N9Z=eKyo zoIETD2ml#HMy*&TV6&Br7Y!x!Y%!IOR~B_ua&Zz~Mivd1Nfd^L8X_EdB40^lXkziD z?*PjQNEOnQ>ndj^W%h;@XSTb@4BzUF1%TQLH0fkXjo%z|(J4iJ^{E0i&$MFR?0Ms@ zW&1z9bY^Kq@41g%o34F)a8b|F)qbZQqv_gv`mIP+X%33vayRo?Eh`HXN_^w#lkXln zapwMm+b6d7hg~WGg#Z93AL#HHz`;nbR{*1U|H5!k;pHEz4yF+cV5}A@Z`@Kh$UJ7j3@K7e*jN5W@r&Ro{uN> zU|&!1&dHzm^(>y$gMFp9KZ7S{J!kxqtbeXg&iZ*S>En3r_CMfBY{f5l@|WX(GXB5( zufIoI(&J(Ee*jN*{6iR0k^sAZXxZj%CN*R)v=6JCQ(HM=Z{^FUw)E1@`r9eX<@LpBMc3lb_$fll9>E{w$t6)A0X~C(qb_AD(QP zT2e8$HQagh*yZ!fJ7!(}(9?JB!J4Rl+hTSi#A$o|+^O~M>Q*+E1hG64v|#3lU^g|h&aY2c=E>f zwk7*k=*evGu12z?7CaJ(%)lVjvS{nXw0f$Lu8%F)TA=hT4UK(#ro@$+efjM^TOuZQ z6%D<8{?K@vj!B0y5jGV?PsN#ZUCUBrRM(}?u0Hza(N6>0J~*_@otXm(=o*_`Xiz#5 z9G+BT$uEh!igW@<%H~_ki|Y=*F=~=JM+*(MLKh<~ddU_uiE1Je!xU;w%9TsHQz{XS zqg9BxE+>#T(bXWNOyH3E{X{V(H}USR!%JHVcyd0Gl!2B8f;?iHvX;v-HC4};(yLN% zf>V}^F+|m`)s8=S$DaskH>?b>(0a(R{K)ZxgOCIiS3uRg$DS=s`Z>9kCnp&A##5Is zT)+G6y~2fWAKTO@A}SAU-1YXwy=9C&diFn(83@n{I}OQ~pvk_K!; zLy%OXk*mdOuQOI!(F64O*0$A~*94g${t(dcm1a+YI?>q`kxFRNk}`v%aCr8hmQOQU z3QAM)5a@PM1(CSQLucfo$rG3F9~!V?1(KN6;}nZQJ~Eg942>_Uk527!Ye1PsL1Wh} zdg;{VA!9*A%#bvV&VTj%v9&RYQh?9Olsg4zHeF`*5L^+zTh_X;HW%)F|53pk^_nwK_rr>$xIesf~U8**$`hR2_;KnN~5tLsDcy~n>S9=aY=a@IXQTd zH`$q}Xq??(5CNiCW1~K_bu?u(7;UC_*R1}jdLDNLl}{-db&;?#jVL^|5>hrNS+e@I$G*OQVVMvvWMpS&aO@yl zn+{^DWJ;Q^p3ji#aJky;7w$c{eVd9#X#2LjxO;g-N+$;0Frv(<#E?u58fNM|DR6bS z3=B;j7BHAvi&-l2_RL!rP_f8d784YFl)+GOs@JScb9&nb>pET<(`nth!XkZsRXAQ9 zpfiAH7LY3ClF1;jWZ&&G(;`qdgU4nwL_(g?1w37V4SIU>EZ@0&hK-t)or6HqT_TdB z(VUq7`lnxf_TB_P6DPu>vru>%k{7mMvbj~|^ZL~AJaSa6+;INx*WcX0W)oTWTgU81f zMdX>_E&@;T^xY%z>>pTmMkCKcy?BVK;!8R#)MbK1%+pS#4k?;!P2`T zAk*vQ5D46BXa`ni0=HQka2SgMv6_w=mrubpYV5JX;{0IG($S%*jrI2E?2dMCGwAx` zXb_pgcBc*-w6y%byVQjgd=i#+C1)c=iVMR z`kI{rnwhKX3sT_(xe&(<`NTxmdz*Z^NbCZE}4bjVowWx?7)@HNm2o%WI~7Ki}FRFKhEm~wzt3`o4SxT;pHSGvn9`RT6d7ra;s98ZEf0dFcA{-@IGH5GgoR5*EX*Em3NMY2qxn z1UWhbU(5y>Lx9`JEN&}nd-1(RQ4)HT-;bJv-0UQ8bTm;F04_jSTfMvwgf^YebsT1)d=E{ z{KG9`GK#?g&8$GMNXcM$thzuXB_j}x#k1xP#U`gaxmNFrk5}+%T%nYX!eCro`DQU5 zUbgj(Gm9(`hza9S(Ad#tnMf69-jO^03$r@cR#JUJ&Py* z55Ne&yWc&Kq3yo&d^|~S{1x8$xqkR8o}@eeJf4jFYW%15&u8&u1dN}aOB!=Mcl#gk z29?Nc zQR)h+BQX$K;xNj&p~=#;mJ>z6?E(k?ES?lJ|8jnx_M@Mjzrgk9@#Ny}U-0D9@t>_v zE)M-ZJn4({I&>t5N2a8csW7;>ylZxc<&8HRwTxu#*^dG$6^tQ(Aikr2sL5&*ly()Y zJ1`BnKYKOcS+S{@DtE-`W`U%UJS1EoV(~RR0?!3}%PI?%5QB!m1I;utCksI;0nAJc z2}H?2^;cf=2DYyq8JX8j)1ChM@!k7(2{tc{geLihn?|m=zBqW?rrxl5p%uf@^vKZomSaZO|mC39+PTF(?ZSHO>r<5<~ZA@6r z;~(v>v@(DX4oju)Zb>-GLU43kw(`VnY`9b zeJ&@Thy$E*UJ$}@@xUO^I5@>b8_-W0o;AW7F$5|!t34X6cjjF%XJj` z{9<#N2joEjT?B@!?q~?O-Hpi*LmGYi$kdTq6wBlT%_s@3eAS?`zW6|UR4}lBF2za!3+Tuco3Qgl_N*8W? z_nlrME)HBa-u(RGok!mZ4RRruqxK}K4S06bsf%wf?4Pz~G~!Uw_yRPG&ow6V{Q+su zO9g%+UBcvRX=;7VteFu|dBvNALiMa+z24$?K>(-%7iTm(S+r_i^FV+DdNrMkJJ+{} zm8A(YjT4Dii!d-D-7)#jOT7lEE2e<66g;V(Pp9IDLLifuX`zyNhrYUWetVUVotI5Q zk_BvnlAMQzWo72%cAnl7z+~iEr4;Y%Pv4yk6MOvTq8+Di-2L$mMda`YK*GCnyg`x& z4eo#c!j`!&ZY$#h1_hc*XN#;(lP|$ms02P43*4VZG8aVjrl#A#w}>m`D<&3uo%V)= z?Fqmk&;(}v%&p5;&2d>JLZ?x(ete)i>9$y?G>Suvg#mNW-dEqM=Owu5+gvW;S z!z2t5!?z@-l&$&jv-7XNxzMTNqcgam#Z9L{Ku!zR51AQVH@|9u!_Ywoo~5cADQj-5 zn6rFz`-!W!f4uEvd+N&d2yw+|uUn)O&YipQrI)sB3d(Jj@(dVGEHmg_A+J%zhopKg z63Vxm^%uLzyeFCz^u1%HNxnf^U z$Bb2bwiH+z)=j0rF&t(PnAm_yX+R}&1Gg*^AnWFf)dj_~r&~1skuH~(CzncCc%cTJ zO$1zIIwYftrF#&piQQoBgOYz*>5 zVu70u5qZ(}aH}Ka=jq~N;C2SGZb&@PeiAv&H7G|+gRP1w0-|FW@iEqIP{tyqyrNyljQGAz$& zDO%W6WRh2Jes?($j)Gx%w&c3^4tiLD^>2N6?d~_}F5*{24WI5KbY|YYj?YDTpXiYZ40=_th1Z z(y}!?DPK}ERuS#8NPMRB8GB-(n@tL~wUvjBRDPn{psHHxXQ*;vnN(n8p^nwI3>QOU zt&mD!NyGaeo>^NDatc5wj^AgG4=-8jg=O&#dU_6pCPcyEaISq(ft0SWc8&xg88o9H zm`aq#roOb!grkDGA=6OT+!Pclx1Ig!?)@LaaUD~e)SFdIoUZ+q<>fS*C?Vrp%SIa` z9vLJTiwRN>5Sax;s>XY@Fc(p zzZ*}^&Cpui&&QKc&o6lA>As%DlTi52{p#M$F3>l{kg%CJg54}lM2jP1tqwXyJ$ss*WWu_Hmcj}*TRZ!Mg)%x( zVPqhL#OL8jd*3hT_qX)z_UNC-lWV5^f+wGj|16$dQ}p}rq%6`=zWt-iw;wzPN{3vU znr5!BOSIO}-HVMHu7rbQ3KLU@woEsYfk(L5Xm-feJkg9)cSTU7=TU)BKxEg})b?=% z5=45r{@)vS`MfDp3k@0`i^LV1ReF(>gv8LuXeJ3G7@eG6YIP8iAa%gK?dZ-R2)WG7 zBIb7=dTGXZ7?cDEL^Op;4NCR%SI;d9ITZ>tRaebGKth~nJjk=c2o-N0Yhz+51v*N? zMG+?I-nxF{@%@K7F~p~kW$qYQWabwR?(9(Wuz5r}%2K=P&E2JB8c3m5Nk_m7wU{vabEGh*BN@h5` zvZ2Q1joR5fHct^Qe_>gSg2LsJy%XCfTB^LnjGR1(jzKZ3LUPf{S!HI800g*E@+*Pn zM+k)iZUda$+qh-YqmeR6;sb9MBczpkKD&PB?qe+#o-77Lx?s$p3JRU`R>$-R1OlQO z99=K&TLjk1X1$!M04#kX0Y`=m7Cgman7UPo(L74SYLyee< z=Mr)dEG$13w2JI*QPRifFnEHX-3!@-6bf5}Cf2N7EX~B=y(62Zw6;fOd^kEw2)t*w zb{`90(O;nxVPQ0Cu5rn3p(1jZCp^H1 ze!@&oUxM_8gn2drZ+w0(j!7nD;4HdWysFwAZmKT{$7O2Z+MbtzB;m8+$ZTcD##%hR zW5>1`k(gJFfh=M0|MX%ukxGIyb@ll=IwB8?RL>o3&;Y|@6c>bNU_}Dm_=bp7E=$(* z%P5LH#C{zc)TlS=R3Jt4T~U=pbe)O|=sXZ8T~7>djM~00*EURp@vuHr?QscA^ z9e%Tm%D08v>lF~6X!exNoiQ~W=2A%I$w2hVY?#H3Z#_O>~b`6d%k zH{sQVduFT2vDtIBOat`HSXTNTOIwa2^rJ0v&wu^?kO`ra5s+949#TuJ!Bq5#z&tj&k%&f%8p=9n4E3yA*`Ipy z0E6)}v&iP6kia*8@#NC>{)7^T)g=Y4zI@lrQg45$g(Fe@OhU=X(9B*lG3~(P8_QXl z6ch^h&H`gXP0VGMg)}O7Hk^!ODUGp`>AP0gIkm4&lXB2BE-aU%;!_#oKm#okL6dif zl?zVXdGz309;Au-tVwV2f;mCG0A#`QT6?w}ETM9pg&my(bGKah>%Dz7Zey@8Hho^1 z*9r=Zc@%9-XP>)u+jv!Lwbr2mhn@w_Yjz&Z6z&^I7VSE8`kgi7EmJ%^r5C&`T4r@Q zq;9F%&NtK2h0*jsclNZlHO%_(+qcYl90Hz-%A=5gD><7%RcVrxJdm+t?JhEC1?G^h zz@Hosh~QY@MxiI;vq11)brKD`LBF>{?y_bJG(|FyQ?QsQ`dj-^@+ZQp18;2YioS%+THKp88*sn)AJQN zokvMcd&C=~M#uDam%;!cQT+51oSowwe`&?C$qmc5FDW+9d^w83g0(wG306knFFC@J zj)JvUUR$&Dzg}0_WNtX6LaSGw-rtNvz%vn`3 zPXeVtDE$G-q_n;M-Up{%KYZr%8(-hOt&r3FHh)9e$c9>L`P2ooO9c5f6O}_BJ@|On zlF3RYg#wI?CA}vPHz{R!VB!Z!h0*}14M-R&r9s6L`uriQAbkTNWu|Do3d5vu+(rQo z@1er-5V%OIbE=1cS2w)1jDnmAr&ijqY%&~+&B@HkBI0w1L=>s+(;MG^Jkh=9lS_a9>!TtKR_m2_ z*Cpy~j%a;T%tBX2dh2WEzklVz+dF!s2plk5;#M8{;*DYmvN1q`*=qBG5*JUR!|Ee) zZqi*;6-d8Uv)CGgQ%#p**+z*QfGc1?k^yhpF!$9Jsr;JyMcu7C4o&T^*5RLi!?D1# zj-)PMbmHhdj~s zSP7b6NCAfr&JRc|0iq{IMSR-uig#Zhal3b({POM(cg$w622r6vG_;i`;)$&hkI}$$ zM|IH!n@=q_5IIB`iAlt}lZ})6MhqH=r{eQ9twlncU8j;bmBMJDtypJELI{@M<`ZHm zWMUQu1q$?ha61FhuVQ5S*pga}$jE?bA~%I7tC-pnFo9^jM&PnXBcjD6jr-pFVBh4d z*-5UzSD4`B0?>y6a#s+M=&cPIqQ+#sMg}ll4l)~u0*vR5^89=u%}_9{pnBuEOE+)c z)i_)sI}^u+Ys=(Rs@~%9hEl=mA+J)b9-JuPDcNWy7mJZ;Aj6CA&2d=NAWT>vw*cr3 z3aBO!MJk1~Jdu@26I&uBQhwS{G#4v$1IYmr&cq@ljSCvf3)O;W=wrqf;s-X4O_W{B$^q%tq5B zR1Aa0#Nf%msL0f@_q_|3zj^dX$Sp`Z_zY?e%`O0zZYF#Ccz?^pyaFW%&1;z91r%hy z09dr>&4l7Z$3`ns0v4Ak=G9gt=J0T48mCnz{zpjP}Kic9gew-rO}uuD?=F2e%YfB}S!BbS4aq z!w^`es(GzT_bjN@mj?JuikXvik+Z<*fgzE=_LCle@$+x)J^c2egf21a#bzMx!wR@! zY5lB)Z*J=u8y{4&P;}eW+NjmY6zau75IU_GyLe*bM6}MOG{_>cStY)*F+dv=lO+9t6iw*1nC?Yx$o`cSX6Hz%Zq`(MdNj#cgD;*uy(8=!pqfJjxQ8qR=GaH3q z55Iro>iye~fSVhMtqaT85;{*LRaMWNJvdqzDQNJkIAT*@eX_OM;%nAgx~kne*SyO| zXLuIui0RaJ^VH$mQ14i=LQdz>a!~@g))6Z9I;c;M-&|GQeEgG*VXiu=Gw96o+vFlh z;wzvF3-#ex%lOiD8-UrqKwQ-9BH`$iEXCpt^Ia5Bm_SiMj5nN21R-WCofF?f+wGj|LpuPwEsRlX|o#J z_FNhWIanaLL8}ZWy&9L>8FH3aNVsgC%o$B-EX95SmM)#yFcj3QhSv39WK?5QX^%D= zh7)MS9+f+>eWJUuBsx*e_m&rQ_W%JKB{$v8MVTF(95ez>mt|&vLLodFkVrAKfi{oW zr$;avW_Jd-fKSFo!C^TF21#CCOrHlP0V!M!VP zr8nR&kEe>1o}TIb9x+}aWb-kM5^I$vwqjJ>*gdjrw5Xy`ujAPHJQg{4;z+SXClr{< z>*O-v*l+9;t9BF?6AcLKPUQUh63|Nm=@EmBlV4`+4bCZ4R8E>sh&L?bdk~ z0>dO39&d6hUFBU?s!H{wc}r`U6VsU~I5Qy!$Kvo@E;*G3=T)=tY<)q==;|0D6SU8= z@_cvzNP8QNOwv#JG# zVC4je0tCTfxGV}2M`0I#{Apj`w1iCMmU{9FpyCc{Wk|~r$!xKeys}?FK3|UGnD4O}+hC(5U-yopMAa79_7cfty zdapO%15Pth#*~S*ktIbcnMXpE@tS5um10nGC$Sk)zN6@+*V~(`8%vF1m4IvSj)~X~je%M^0)mAi1noC1w8CM<&xb?48ak-a?&O$OAPJ>>c z<)(o(jK{%CfuspTP)k(&#D|yeJ-GiDo=_mPiD}mQk)Tc^k^?&in_5%S5K)xd%iE}G zl|U|!t?lWU)^5ijNoSAib{}67z}yWSOX-L6~qj{ZP4W5 zCyWNZ(m6RfJL#3E*)^-@`HdJr3DDR1$x5%FtQN!|o2*8j#L6?8G+ZJKE?Rs1m02-w$q*y`{&G!i zII4wYT9H9+vewiF<)szoQ7r_b*`=X5!;NEe?Rj`LA4^bBLvvqURj+0|FzgQ2EqiNymkTFlhvbtphzL|Jw(zyHBWXu=G&%=xIhhD%NP^D9D;0P|28^K$x=hSO)6#sbh*#y+ zZ+)-2>a~ksUH|4@jHU#19^epKJgp$5VPg>>?=jF+1Y|Pd$h1nDUMG}W%hyb+d1;26 zEfpGc3cjvtaa&i2Z5NU`s5};*Kvn4o`ii3RDZR78GG~3-TNsJL6gG5dLxSj-$Chjy z+qGzHutA#(42LBQG8s!@F-eNT>S}i(gMgueSCv5`kh7>GuWmJfRXbRhp@G<*NR1Y% zU|DQi9y*(fLI^x5V53>mFSa1_TZ8Z28VzqfcjfxS2i;n}orX0>eWB*ohPpzUC|{>{ zRy#zNGHb=qN~1!prE%orJ)yxvbL@0Mz-e(f?30@&M|&EjM5&2Mzz`@L7EhZj);LWC zQH@#+-X;j(UNKr(C^k!|2EJ$0YbV~1*&<033Z0AaI+%HRFdpA3*G$>>)>boE0AQ8w z9!)Mw#VYQc9wFxBW+O#}%xpY_)-)&hq+CFkqO(akc@he6QK$I*e7a5VUVHV$`t6@T zxcSB1Z6#8Nn9UpLjmJtWhb!btr_>$`gT{HwXj|XQgIX??DAbg@n5y!ZySxIwL965% zr)=D{YRN>gfWoC>@<6gBL(OGN6^Sanu^^GK;Ia^!;?9`JZ;Nt>a#6*hufEuwpD0&L zsFji7eA@?eH``#+wKCmmp4-hcToy~xvjJ&PwDU|;DC z`Q-O+|7rw(^(=pI!LP>m+MmwlpY=Ekex8f>w}$>7_>=#+;#YX`=kcG#Q~%ld2k_*P ze}FOvNvI(JPhN1t3`D}yW-rS}XJnCrKBtmHMzC#?WTS(Un`;zM$hjN~-BRim;7Ak( zcGq*}pIZ6L`8_Fq{`^y2e;!XB*zgOUd^-NK^FJ{4_u)xAARuMY#Nhs=u*F0shQKVV zYbf4x{V&(Ry7$Nh-WbKB_&_+q=70oNE|rJHcnX9{I#X1!wv`6MV<9hx zip@f!GPCH3!`&=BHWvcyB_%6u!@_cSV+I;k1AHKh>2RD>nXcyPR?AImFMa~p)?Z(^ zc<$XbO;s{%HcRG@CL00jh?qo72zXVL+mxy0>$*!F2)e+G%E&UU+gk*Zh*(6mfEkQ$ zzIf^NArO2*qSb32;1k0Vf+1c3}342E6{9 z416FZ$;*cGnwM|gcjm&Cn-3m*boJ`hkGI$6E0`*t)$MOt+16^twc4eZdGNn&0V;2S_BB{Hg zxdMDEQlW5dzNf6QRgM7esWfAKTdfz5$zct?xy+m^YMeG}0SMot(Qr?2TP|terU0;m zV=~z|fr!D!XCc__4V`itU=^gv0w_gHi_3)Z7G1x3GgXP4#=p-sLN(9I)8b0%2-lS zppp{P&DE?7j?NZJMrva|9UIG}wY|1uX^}!j5QxP}lc1E9lLs6I9mC6-s;I17&a_jz zLdMjf+ZXQ)5D6wO474dJTAbKb(jMcINdy$F$}ME)W@Xzdx7FYgL|htJXTl&N6@hQq z*}U+hbLYRl@!!`QM%C;u&(4W@T7q#UK0Q6qd)KC#QX#L;;<71B@tLpA9i1lQQxa}8 z-DGs*(b>Si+SA!sGe@Wr31tB>{QX0$XB2erK43SEuLOB`yStnmCfbu}vhmd0q z2tmZRv$0=B0958QV$EdZLbY{$D+|1P&wX+Gn+J~zC-#4K`QEn|-*4hFIr3mYXww;d zVwq83E(BU9s*o|UdAW6ARU!~ zA~Em~CX*X|^IyO15F)Z+xjA``3OfrDF=<036g4aOymx=JY_zZjPJ3k&DD$<70Xq;AUbDBVSK`L&?X5?9xk2RDenksn&Qn8Z^GerG^6)rJf z;{`Rnh<$Xj#j6DwnG!dT9W#?GUU@za%>#WBBAHdV`1scsmr*m}z%&z7i*s|c$rNt= zOs^r_E<<1%tXSc^LmNtr)5OznJox6}o$ok&N6)rnH@|!I$pSZz6^>e}hw`JzAPSvH z2C0NGpOsor7@s@PQdeuFYe07V?6pe=dKL!wpcah~S%!`u+0dZVh_w(`YqgfOR}knv zrx-2}_RX0NVgUjgA5F#$(D;=FJey5tl3F3td5A1GN#aA?JIdlEagYUjI z1k2p{lZQUPbK}C+L_owc7|Ux~%dBv~C?JJen$0X)th#hKKjgNmil&%Y6yLg4tG92d zcABAF1kWd^+_i75)8Ul!uy|l(+8CB`MdnytgTP%;hRn>%V{kyl)u7QisJz~f&ulI( zr?S~Nl0fUAYlf#V*#&5Je!G+qQ&%hTS-A{#QS0n37f_?i>>MIGCnp2JhEbF@6#*UF7&Y>U^!{FOo z^;6>}TPTvK-{pHAp8Pw&2v6&iUgJ{?^3Qnk?*JqG?)v1(4DEWs^YJ8h<1cvU>As%D zli0yOk0+zQ8vkE3Pv`Q_crpsc|FiYU|Jd{ko_sp~vv}%1hW-GaJogV_nL%!n-Z{$-Oey8lXnaV>XH*>e?!7s-inc*Dk52k~$mnCsa@rv;dDMsZh9f^8#GGIBF>OrSg=1b5Gjof}8H`(L|#>+!?KKNW$FC5J;P zc;(Jt9zOogAW`{hi*+2KvHs=j*FIQ3vlK_o#cFzA9FOT)SbfxI>gtUv+KLLuLUtA; z2abl`r4#GFy8U`ZtCxsn@rj%_uO97=NZ6>{v@SYBstL|tGE~$kq_R+2Js+bBODV9d zEF2QWh%VYPf57c5)8w&`oOngFMlOQ`sOTZU*(y#b8kxSSkU|#b(6ZrdN!3V?8sO(l zq}o_k%we7@+VV0f}fE#;GqdUfX;7w&$4|M8FC zH^pk=^&|NTp;@L~e&zbTd#4U9EHsGe(G`8&9WoX4kD6sFd!RU_WF(e5 zYWIBi>8>`nR)9y)OAcK9@Wt^khsHsPaU3Gf0Tf;gDc2G7MvIdjJZbe1!_=>U0a#0RmU|#&f&dC-{2#BL_WSqHrYKY7NHC+Dk z?GwFm0#YP`5axKg_KqY5mz+D=(imiNv{ZFVb-MSL-t7A}Ff9V*%54|F`1(H{|K)#u z|J|$0MpthsFB)HW`27p#E`Im7AO7~}v#qOJW!~sus%83U>xyk1i-3lEfA%oF*0vorgl(_b!g=R5Unbs!}*>dB9LMi{97J1}B?diAIXt;^|GvHNAOHN?txK10 z|M0iJ{`-T^-`_pn+Pr*X>B`r3ADmt}_Rh`kFMWCK?H4A1r|rtktrg{!Lt`6mKe%%G z_~iJk;h8HB-v8G-U)_KF_{PZ%Nru@ID>lrCSt>#%OR3s4?foOKFIq9+DR(ky7y-zS zs@Qk%mC;T;imumcBzS+VS%Ztb(g$)+X>_7}WvQjN6rIU257vlh-F1*=6~WU54G6S(x2%rl+T8x~KoEx~r?Yy1IM*HgK~VW`te#fV||J^z>AJuU@C$ z>#u9lRX46*8v8S=IEoH{CrzgJd^0q3>cGgc{lES&GIHT?`f^cKiehFcef`TFyLP|3 z?UfJ5%70%N+PUMSU0b(*`qe~n^ziQO`%kCVy!7&$+uj-({@}aO^oG}tX47vBg24EG z>yD4#e(&>>!+)GfzrTL{rqsczQJTpWW#&ZG1MNkqSMoE3YIVLMNx6zr7iARr5ZAK# znFS%AE$0e_dx9cqX0fy|U%r!{k!z*fQ?+}!YqO%p0IZjUB5zt}nxqh0o4YwanVA@$ z%>OkyIh!eFZ(f}$3a(zM-5Sr|xN`CQiJy-QUzp9^zI^Jpk@Lq7U7D`K%EYOCKaC#! z;*-5!4<8(#J~KXb{p8@Jbmhk}2paWs&Wi8jl(&eW28f>YaEOCwS%zCoCYUARb zmGva!zEXT#EkWPcay`jt$IMY9drC zO2b6NG#k3%G|qC;<>Rwk&OeTSaLvl`Z#nS)^yJPL9`!z%&wcI1vFQ!`dxAdy^7z*t zdK7=i<4o|B_4Zl1s8yw!AXHRUH)KP>7cb1_?)+00DhongE0(8=vP6heh0OCnC+sqw_*TQv97k_x z&@~-LCZ38T;1WyMy8u~^ZexIK6x(VLX_2X$MDvJ_tk8BEk&9dtIS4~l!*Qp@qL5UP zp}Vf-*%}7WRUHeNDqyx{8hm{<)8bFrX?kJU zB!qfC^C)8o(Uyy;Z=0ARjD3E%6JPg#o2L@InenXoVmOEJ}j5 zi+vh4p^sVt2>^vYMaUb{GXAA-Z(&O4YxVfLNOKb1yZ=b#PzG(4(iYqJE-p$-siu+E`_qDwC zZsxucd!E|6_X+;YJN;OeKFjaz#gcmHWrdGkZP>$qO3u|;j>M94HOBb|`F4Un|MJ>1 e*6V+Yw@L7n_4t$8yJ}!ySedtEn&%2ZtR;MP5e7T(som8Q0HWQbBw zNm$;g8?a6vu+Gr15lmaH=kNbJF)@*=6#Mz}=aLb-YJ+NnvNPYz(YFKE{Y%;jd8Pud z)wnNHD4}VtC95vE)Xi=Vt~{?Aw--#ae8i6;b*P!NtFE7RNbGZkH)+U!J(+a#b)O}? z$cWwa%#1NFor&+!(#Xh2%lUfC54&m)38^$S%SX*G5B_v@b!DFKUX`Ij$OxpV>6PB1 zi3skP4+;v>D@|78#t&$1aNqR3IGMeX+TUi%-v9TZiO&lcm%l%|qC_2MJS5&tPEKkR zcGws{e!O$!&34!KtwtehY|p|rGOVM|l7=EoC@ne%GwXAZ!A@+5s2Z0@A8<9IN3<<$ zzaCg#b1lJqNj34UYiV(;)Sqee#M)JV`}XbltaB62-2GNu#VaZ*E_Uu-?EL<1FUCR|xYb2(a(H$Qzqh-FHMuw*efsq2)X$Y%#>R%N@UK0u5xc$p{S^9)!4s!FUKebvg?=quoPsBQG6!yxMmWbv%Vazqi+ivF~Yp^HE(}_bnD8_iv}YF zEn>~m5mn7MYTQv>+We_RL2dRT>uwG3ntpyQniyo5TvXTJ^Rp8;kqk)VX76jn?K3m) zW;a`!nlc8gSz`;w?fL1*68kI#)8w_(xD(wPd;}PHQ|~pqG)%L`DAGm20e;o9;84@w zm8QmBQsUNNR60_!USCo|5f=17m?_59sAh5lj@;(KPLt&$RwN<(23EKbd#(SkOWk51 zBB{|n(vn;Hv&E7{JryT~Z zg%q@e)u9}qXuf6Kff`9!g0o2;*5TVy@x&ls`}UnX4NXnA(g)xwthxTGcc?Z9>zFqc z7!uz2_3PI{PpqLg-r4GE@4Qn|zg29{0}M)79V@JkQi$sMsLz|%a$MRXe)le0Y!6+O z!swoHg=pp=+*EdAC@w9O3{`mSw#R!+?JRLG>MU|ML${K!Z$CK4{{vuAt^$O7$R=oinQ z=VHFN)S(bEy1Io}Mq-4pI$4NpgnW*1#leXW915)=VLrMjXXg6pKk#b`&JwH*(|>e$ ztOV2e`T5@ra9@?t9Wf{?D6zw!K91X`rly|xnrNX4vEys5hi~5$#PnF`>MG?9drMV} zuJ0V3{`&Q0Wrr`df7n(YIbwH2!m8|B{P%25I1jh6d)?p4-QY zAK}EzJ9)rS8?iGoF-aVJt8sNrCS^&V_XoTyv|){TrN{H<&tJa0!x{^(*D(D<_?Z|> zLJ-_8w)3qpgEH=ZL0n zjQ(y{T{2RVgE4G#Yv7B_DR{!27;)#9OP#&Qq@|#<7>h`XBl+_Z(* zG>yVD-xll|Z)$(CYW}ptgBFkKBkvhoVKwdth1e&;Om7O(2CP&2mkv&tW4bps%AEhB ziwY?z@lLQ67Ka;$UEAMxtC`G?TdQ}d)Q1bvv+$&5Qb|dvzI?s@&%udJ)p!^s+poQ2 zC$U|xCU_ii>5+Fi=8Jh{xMHq+hfCcjecs5NT`x(B*vSnjtEet>3D$JA zf*?YH{(T=wJkZX)SMMn)kW^GhzbGY2nIgmsxS zQiM%A)J!|jIzel$)QA2eoBg4x>OsY5hQ`%8_RPV@Wj`EhPC6*>A_;@wE<|$D%O~Vn zSFO1!6f3PlcD-*UG=G<;RKq#Vs5-@zK-sd6DQM8q7CjpEd3(EA^j=jIJmF8hr>onX)o1x;z}hmr%lsw-QY`hyTw9L=LH>5Bz+uO^z_V{t3g>x`8v!R@eDXnGE``l zpPVo{qH@GC2P5zwr8F5k3Nyhx_s;cda;O^b)t;i()x{6+=j{(-%jqe6!f_` zJ18tHgdR|-FP8Cb-f7jP&eiB*ckKaBGE6YjFHU0bpD;OZ?i~3uCZMT*Xg5rsA8zNe z6NktK1YIYVZJA}G2(xs7k>gTVbWJMr28t94qRn*9NlaM%X|;h!dfDvegZ2XKlR~T( zsta0?y+}FP2Nmq6BX5U1ld5qzdgbdm80hnHF*%quS1&(5KQR_)ehv~gMx!qjv^e8? zGw!$_GKY0IdwO0|Nhm3a%yF`MVdP$Cm}UKqYAq&da?P~>TRgbJMe@L|PSRWBFXpbJ zU>c0~X@`fp$tCx7ic7xKe3;zmv(!#Nr}n?toRQ=wF6*!t;UX=zU9{$hn{~wt6QWU= z*gMw`r`)(gsjOz)UY@mAZFFyE=Q$5WUqqm0ncnr}NVEz}CB4#!98CXTZz&W0RK3zm z@1InSYZs&D1gVJa?d=;qcb0$t`_pb+)#QEjx2EN61AvLJ`pfgv+XwCDmFUDi#oU{j zgGT(LrUJeH&cxKpjwe*yz`hMKujH4dAg*?N=Fz`O5J|6_{sZ&drA}Bq_iLhvc5xDK zKTPV;_4rS9@29@4xlU{zJQGPDU4J2-0i#G)*P*JUByU}xw_v>I^TQPdiQ!q^pj{f0t0V@lO zFcUcq53W4ZYmLI4qqlPfVv#w-28(4XIM9bGRK}r7igoO4Y?P>iCnq24>Z)_oj)y$l z@@gW~|KrxM`ydCSP5p8F_n_~-kk>2Ke0b)=FWsBpckJFMUV)P7tiePEvipRxX&z?Pt=aFQMEqIdHhxjn%P@~TMfHOYJ z+llVToP}FK>u>_vM^bC{r*V73-MzgRfjTJwSf}i&#S|0JA+p)6YD(W6zYWhb zUH81uke@P4?!4QfW(T*?c+*J?&3v1MLL~tXEpI=R^5%h3u9nMd0Mq04Q}9Z^4^9BQ z2{3s7UF@U0dnLX2hSXlnDo(ZS*GoBwR*xKg24NckM=B%rItp+y(VH1c|m+KU9? zPC1CqW`w7rRT9ul_scK6RS-!hSdLZXqPSi5C|?`etSd4Hv+pIz&lLt(?0NeYl}N>0 zz>`Pb##D&%mY#)5gR&e91tDN+T@0#y-U&@J&l#SB+4%jNDuO??e`QC@gd9PIB%IuE zYnd&4mi@)at5v#DluZ^ZY;2ISG%zq=Dfp;jG^#6So7$#|F^<*);m;7;14N`!f6V^# zwg+-6uPm46c7}$AKU&39t@&}obV0cm+1v~0+<=!)&fXOG9Ph&TgDzKpyyU_d+aoF> z0t}$B5zgbI6qv^2_5kVTov405H|hNdL;!j&9xf@lsCJ4~f;=x>sSb}w`bJw2(e8d^ z4p4+HbF)hQzelGqLvi~6*VIg2p^nUXt&Dy%unZ(9clh;y^&$h!?IUfJg0C1;%%4Ah zmNLP#sdFdv6n%!LS)mlEhS2L;3ND|fo z&CAB-Dr>A9r@eziV4v~|FphD17+iNUH2%Cn5hGSZ;joV}zDFSmk0>;mppvm`aUylX zxx?Yi7FFYOG`6~i{118Qstn2=K70s>J~9VQL(+S_bYxMpzKY- zt#tLAFad(5wfg4j>aItH)zpaWaeLVZGM25(eIMcA<0L@k0Teehe4ClM`TMt%EMU8k z-^a&Zf@#vx)CEZ%&BLzZO-)T#?} z!y9fgrBk&PE$3bmtSw%9s-u8Yzi+s4q^&v@F?&DVa=@TCZ@9F<1fSgaw&cQ}dOdxB zCz6IV#KF-qeSmvlXn0tQJ8}3$1}!IXAi^O`J}`TJrqlf91gAPyF_*fC%2HJTnvNEV z<5>**(yDx}mYv8G5t<12m7n-q>n?TD2Cm)|0Oy>j=p1TZiln!_BM6cyXNve@SDldQ zY83XKG(?t?RYFsDWMv0e17>`~G{AK@yR)135-cnAiyRd1(?`hLUPp={72gf67+0iX z+#9B)uS;=+^eLynD8Q}_*^v|Ci`9?Y6DqRAC@!v<3Mj8A9P~5=Pt4yvw1cLcfId|8 zZz*3#xvFDpxuzXN8IT5LdYXKWg_ua0fBhJcZl5JyKL9To%G-=p* zCro7fQSOl%n#TL1cCkE}WQImi8++Hc^*sR5+Qo<(LhaCZ)CgP}*%Yg&u7o~I_CI16 zsmxeHCqKVT4PC4-emk?k11;3?cg5Vc_1ft_in%gea!L0Ku})&hReYG-&DK@7)6|qb z6fKlXo{O<`%u~5p3NXYGhChT(JH`~Fh+RWb1p0Gjf0x{@aHwJ4B_uZDPtCF}x*QmL zs-*g1+}_e%oJJknXnbQWg~5P76)Hui3Gm#(3E;stqX1?9@4(QZ`J$QewTeqf6FK`o zmXtuL(?#u__!!tT;fY@b=n)~0kO3spWxkCqy#JqTSjRR{WLI%=BaL7260G+tMq!A~ z`MrK5)zq`Vh$MtNYg}ZxC!eME6X*`s~DEiw@hDS|`x|$!1G)hBXK_lO07w5#}#CiDkVPv)9BadJ?akXI$}| z@#x{lJ9pJ$+f_s;BD8Wz3@b((^m((w{UbDe;3$i+{8)d%@Lr$yb93{n)>|1Ga@j+D zeJT>H2VPAmD&UCE934fOV&pk-ExB|!ir?#9M`VrIP!5Fu~(mLghw z04m41tuS6HAmH+k?8M{tDM4gQR+aj;OE`?c6{Iqs!y+nr(Q0B+*Aa_It%LR#wOxjA;2!$C5s{Uf%*=dzPOYoT@P^QNeAw zr}!#wBr7MrR2d)U!ulR*AgB%?O=W8;7h>TQ%L75&=P{82IJv7aK|HKs4{MzKsxDmVL^L@vQ&&CxGHUoHdIJHGrA}nll#HDer za??ctwfAcJv+co4N47^X6G66kZ~{f$6)*A~u$)Z)(qHpR>BvZV&OEPnJjsAqeGJOY z-5o$mR~LbN5O2Q@mDeUnCI1-IBs{cvk3>Kj_ls4_Y*+*pB4Fm%++6GW9!%d~#JKdt zi2eP2V)-nN!{Ieo5vCaHD#i%;<1?vb0y=UryvUzYj9UsQg}9iWg-0ePg+sB5xxhHt z1!jOACU|&IQ&7Ihtib8d!hG?N0JM~*X6@s1S%4xyg%gewKvPLqcfnnad|T7d(D3=QBHaqH%B=4R-;*r3D!)G0)VwfiX>0_H zy=d%`1#@sldaHvj=hU;wm=_VpZ%Q1g11HTxVz>P53m!5b@*?j#LlGwOpOkrPJ4dS$ zpe6o~vfe%2!dLIK7rB?Oy>kSQK92<`EXLxy{gWUhlV#1Nj;a>`ldUjtkxZZcM|Fmw zh5oGw>LAa6$u?5~7K$1JzGi~rSJ5zq+UEt5TDr`$13}^RZE-}YmtV@pC%ccq>9?)$ z-ASJv+F~L`dVO7Ug)5en zM>qK~BWZ|fIj-?rn*>cX4In(7gn!D!fLJAhYOW6>c*CSwLPBC{dfJGW?gM^+wEx$# zl9Juni^EvxHN&==y)seKF+ia(q(rNOcllHlx4AzP1_fZ(3?Dzfjw017t=29cnv0vE zk5Z^MQ23-&4O1o@#L?iYlLeN0T>e6#tO6*~?eSG*xIVkCJjsEmfC3oor~b|pve{Ht zqt;dN%AkV+aYqvFM!?C%3kU)(EY3oqFD54;BPWnP0R7h^?+Md6T0zUyzK`v#W9}Ip zf+JZKv_Z4CG>CxvIZq6L+{TDX0oa$b6z-almf60%?Y1RRRXbs&O&V-N>PY zR(^KAR+jmQ#faxP6eJ&|Q3{K%-5SXM063M(tiY~089V0yj1HO;J!fC1AjD5#(gc!$ zND-im-!V2*%*E*#b*m8Dw}R%aEkU#a7weG8{7#f5nXg?u?_?{IekbD<=u8dM!0H@v zqwby`mz22N)OzX5YmvCmLP11&`7+zF-s z?~V=BQf+&@bs*E5SlA{GbaHj=3<(V>obvA*T8xhbYLtU{`SRtWKd{)?p3)H>4Tbap zLR@Qkd^13+PGX?>0Q_|UqDK+Nm7x*VVb?@v#XtBn4+?IB={ z$GlqJFwK8^p~nwC(dfED>MhcP*p7RnFG#mNBntd~MltK^nmi)W(H&TF0hQgTOfOxH z`_7#^yIY-S9ug_6cYTpb*$7Sd-kEI8-VL{_i7HWDvJ%wkiYL;x9mJq(HnWyBZ;QVv z;8@z~f`W%bpu^*RoyA$&r(WW{?$8j+tuJNl#DM${566~VfB_NXAt?Ey;75WZXBi-Q zaNba)R#lHcWqgG;lc55bdGsQ4(8j|(_!5_BhQ>AhxpY$j;_R=CA-X;D2%`w8)-aIH zS}AU#VV%v!HF5R|=| z2CNfPTW1j;_r02^vRoS%@e}y+KoJ1!(s@gpIxONk**(IVMYo2NGbuNLBKDTqi;;&W zFT^iT*fa2W4_fD$bVA1MyFCZY^23O1*;rWew6CdS%gf7$Xpy#E z_c9S}j^``OLMyeXO(L<9(H5hkYcXd2Jc z!);5!BM_&076SMa(bqCG`agcX9mF4KU=J>U8~poHf>r8n*a+42S_Fzq9H(?7vJ1y5 zwBZ(0OdQ8r-0%ci1*C=GelU)7i*;7nIK-q0PmviY|Gp)c_dftXiMzp}Ed+Kt+HnH`dqZ zSxkg~lJs2Bx7927cx5epG%tng{E4qI(XOV)I7awXw6;F_FAd~{^FdU%t#$sfXz z)*ekWO-+(UqY=;(f_&v`>=8L;C=Sog1?NI+ggOzauTn(6XAJKpuDOe(D{#hpNKlxE=;)we5=rmhpo=1+WSwg9W81tZ!AjH| z7e^C;_$pH|8gv^J-&Ry3Zl0B?BL+o-Xxlu%+OPj3`~){-`DJpUm3SD7ii%cxW9ONJ z7@Z;rU#O&3HZ=|4S_R_8M}1}yPb!aG&ZGr}22k{XwFP%Mz&n>ZucmJyvIVV&0ms9e z2ZMuy*_@Trnp~`sUOE+{Br?5fC481&%`0o0nn0}Ty%S8KY9q{K6^Jtc`0_i;GY1C{ zxiber+lV0+m~d>&Y7|S`Jla_ScmdAM;w5z~@txdb=A{jAF+F`)LtC-!d7ov% zI8!-^X}nw8$`1AF2l?KVMtbi#5SFU2kg{7<#=>kouDJ_~ypQ*0=W;1v;fF7u)PC=eM?O<`<9=i-K5`a#Gc0aC)N3(I0 zS6ABdcdU>c`OsL0(~tE%ecn(XC;A6o#9(KY$yp&`) z&x>=P*U~kUM#VZHL?h(XYpnF&9esk?ys%^`*v8(C0!t7mrT#n!`+w;|t@5PxgS?tv z5lCyH{&ME!m#_Ozc}RR?51MwcVTyaKNR~2Sz5Q~2_Lo~jvCc0*gvgygeCjQLDZ{^dG3SWhxp-m25o=Ch)GWu$T55hWd1!U*t z?0_g}-s+@vPrBwjA?kF!ssu@&7+~g0Ad^L5z68omcPjWdU%6;;l`Tq2{%me`@S5_2 zo&kRH&!2c(Pm+X-$kgxg85%F($u7-=(E;Sr#2YG8#6@&aDx z*?0+Kz~Q`;KJbBXC=;-h_vH^_v21s{$2+Wg)+HH`zK8Qf>;aP-+^c4q)HtsN5Lbsp zoN*!R0o+PUXq7X6WiXQ&E5yPZPIWA7tdNi=%|5s75$B|!^?t;z-HtRm<$Ql%FJ&q7 z?6*yNdbnJL}7X0*K$+3P%<{d;WX}dU7wXvQT~b zjv`VXFvGuKP$B2k%uFEv&1I%MvV5h4ym_a?#LQC&iU4a>)Bq6qj-$~a2Lr-b5ejGO z{M?s6^(jn5m$Z8zMVr+Hwz2Qt7iP*oAfy0VCVs7v5ylVDj<@_%L0QAVS5zPyQaWO{ zuszl)&04vZX28u`K!4=@PYnqDkl0s|tie4ojRx@dR z7}6+X9*i<;r5=c#tF5bRJ6FDXD{~NhwepIJ;^@^kp;du8^7@*|n#@194}bj7*qE0lTws}{3I6liRhAWA*^Q$j{bjeTu9c*TS#+tJ zY4P*(^@Y$tY;3GEGJ&;7GwQkyyd=TnitWkvS?jkoxmKeVz~MWx{=$K< zo6yzc#jU?riSZiwY+LO`8`F;I^&qd9g`(d-(c7`0q&` z@`rIko?Z2?oueN=eypOuuYCgS{rGVZmO~wEPv=0!xA^2A(4?7!NYZMYz`PMVj!y5> zL+X)G6GTlSA-kE30_ZF95XGxC3oif|CH^Y*sbJce^bQyi+Qq^w6oBqBqjJV!h&6jk`{wOy8}%e;pKd=8+`n$0QKY5D9Mc>C>J{lkAY z&m|Nfgu<1@7lVmQ$)z=aEe5J_SLyTqnX1Fui^Nm)hJQVXQOt$@WnQT)^q;+m*=zQg z+ebMV5I4c4+dP0~v9K*BDRu$;E~$TseQiF8SYP0_5RJEJr_Fnxm6esRQXMzsrnEw1 z`0js?RkT_Qt7H7MKDOscMn%HW%PsgQ1z5stvrkm|`_XkR_M%vfH|Tp zxY;#x%YnoM$>~>TC^#VJ0b&l!6NaF07AfyNOM3?QKbS3LtZ;Oc{YVzWup48D3*>F= z?O*FPD6i(M?RgM5{wH{&4=aq#E?W-0UJOv_1tW4$M^{oz zjNK9hUR%niq+X1YLZ8VMx5>AO6XJ@NglX?1jkljL$Ao@5#&_-+qFJB87=u&*#yGkQ z>RTx7>&4mZ#ib~P$Xx6iBnC(xkt0YpWCyPhynV`Iws+Q>55UC34qA6O*iotSn zEX`9rxsi3#j;f|)bUiz1yN{d-e-zH|Bgd`Z<5iGQ`_3Smu0F)jbT!0Yf>lUQsYNdT z^SC|E5z|vlc$HHWZ+XYVx~aZDvTIGe_cwe11cd z?JxT}TLH2)5Ey2Sg;enm)U!Ps05pKg7F-Yh7#-@;l+5o+%dh+hsubwuTxRdlD8$~~ z3?d~AdfTtVqj3A`{c}$b`z}m^HN#M2Z7n8Q6(k8rIdu?ea~;w8v5>tdV8BXeefj~7 zv^$V$_yr`^b}~kP{BSk0;Z)NfkQ?R}z$OCAmGS`t)S{B%1rr}-o$QwlEZ^c3~ z!LmO(R1Ss^?;W>>%Nmai)y<1a;03@qhM6DM0j;B?#1~Rry{e$ESjSL{2;8z!)IzzT zm}>|bdQyD=sPoc+6Bo{LusKU~6@t(ZpJ6~UNX;`4mny_>xTWhcm9p=q2iu0wGjOXK zsAFg()rPev$QfCLiL&4ohUTe;N=kCr%<|g3Uk|RdiF>aA6$Ywv^He-d%~a%;`U6ym z$gksLCWBs_H62`|(X{RszYAY67VgT3obU*u@cu-$meAM0Vxx z9&Rrf@*n(zm|&X$vT}0WMXXj>qIT?Pd4}RLI0Tq>o17q4o)90Kk&$63_@oi9h=EZDS&w7^oOx~`x&hf#ck#<23#8ZSZ8_uF69AkoFI$VTG}qU+yU2Bn zZ61IJ?z8H03m+o!&XCZ7DL)3G+wbdpy+4s*;W-}g81Q>4G6j{1@p0!mLx1j0J15Q( zK^Zw6=PvLKELmC2dK5gmA$;nr|)_3?OMXr{Pnts`ua{|zU9;Nt5vE_55_E)O9h@^uK z2@ui9&=4e~H*P<;0#GX5V$=XAq0ZO*i3z$xzgi2WhpBU8fy%pGk(Dut8Di$C0qV&a z$T+jIGd(rcI+dqgOvxs-TJP)P0!a$w5>X=ZuLwGDc)tH*bi0BR?U4R0+q?9obyt30 z*VjX?T2%9YjjZn#-eh1n;9&m1M@B+3BiAGTUfFS z62WK9*X~>2^W7h}@ofQxq1kKiS6|{)AXof!QSU!wt4fdKGlcU5^NDAhSK3yMgGJN$ zRE#B*$P6!Ri5bRAogu~5ke&t4lYPG1-)rYSu);+G_#NQvAHNGpIdiH>LDd*Dfm?;*)9vrz5H#Z;=HAs|&A15GmROt1Gy%31|Ra>;!jV0RlvTj`4%Uf5U=m)=^+eI(HA-=?+@F|pwiLZ z+uEWk>KVlT_}ijax(9a`4)Oh0JYX6}U(;?+s+vDIp^ZS?XPTFmt7-oU(M4}X>CT@X zo}Lg|DDv+{(+-Nv<6aJR3$+Z!bb8`Ep!rB>Dk(1RwY#cSrU$nhqySJSp{s3=Y#wlB z1Fr;KDMk?}iaP!?iKkcH+}!>iN%&Ga#N}aBtHy)d(2s`(6LYkv)+C-}-<$*-)%`1W zJ2>*}>`W<^Rcn)lqGx=Z(LeNK`$Jfn$<{2I-F#T8`--6QUKRU$w-@tqa3P>Wr&`q+ zl;Kf_FKc2tS-2vYxZmKC?D~D84(js{$G;PVxGe(a*)-^7_ws3+SVel*T#wIuzj2P? z8AfBdAo)eKcKh%pozL9w?sP~%iN*ZB^2b#iVldC1!HxmmD`C^a6%GS27m!zBP}Mgz z0HG>6EV)lc*XtiNN8Ng^@pXo413tP^=Qq3i*G5c1eGB)q-5 z4!q7}VI9e;U8WCk??b%VqKNf~F@7R4yYfc#4UG!Twy<$Zq+IL#1CnPAip zi+|xwUkQ)lSe`fB|Ujlt1)29MxJ}WC)0n^&$yWtmVM>STbMBrp1=MDMslQn4`c+mqDZ9q(5l(pnRcb2?Y*gKLyq{D_wh z7LQ!roSdW;!mhxQL75)er|^!E52Y>_5GbBzX_E9|St=N|{ji^sLGBu|djnEzb%xEp zXK$PdSdoDu!s!Fj1Hwl`+lZ#ob*nckfB$}8*#7#E>v~cs1hkl9^b=rnhA;@Xg9C}- zjER@>WFpyZ`fwbSc_|3>V+3Mk%IHA3x2cfK)xY_?&ahIy^Fc^pC)11c0WjMD!8pqE z+Ym4#7%rItai$>&5(fp2I0FPSSY!EO_l&g&e}|4G8Fq_2B%nOOLzjrh7nBv3kcd{y z<<&NN{CHtpo#hb)Lh|aZoPYfI_AxaMFi+v{2?b9eQxB8hr&tG)Le0<%%k(;8q)msZ zWW;mdeN^U2>@zJAyL-0+g8D9L&Ge{ff@mH&S{sA1b9kt?6-0|!&dK3;*+qI%f-&}r z(SGIiwY9anzM21B}2(8YPoJr6lMd2*~LZql%@Xud#dO?JT-igRzv^Mxzke?N>DuLq8>Uq zC7$%TAwNBLfQ6R%c@A3AVEUfqGiO499y9X$F@msJ!z$%Xe5SYrmYCQ6Z^l%@&mFZk z!gsJEURE9sXf+-8U`hK76kZm|$;-&DpRgaGpaq5<`sR1+&s%`EAVi-7Fd-knb5E{lbn3p*A2IRg>ga5&uWu|2Vh{(l4e zmp*D@KDaN#N(=Z)RIr*Q4p}z*`w&j%SV`zZqfpmnqy^$CIU_uI0x^-qk9U>S#HiB$a}htXu7WfS>|&r*vJzeD zkXN4l+gDf_9vYp=od~^DN;EJ*4f$F9z;yN zAPWUl=nToBFr*#Xa!-+>|p{0JZ6r+EkEeewCaJFX1Tw%@F)yru5bf2D@* zwA<+aP_HB?i7|4+RU8x48g0l=WZ~)U{Z<)0)(lLX5o233>B<_e01E_#xmgl{SrC-J z?|1y*QOaGP|#Do^0e^nrNg#ko1t=g)=G2NJF_ z$e3GVZP`}upcLw$yNa=Z?DKV2)5q%3%)|tqRicppR;U77R%P9p{Ez&Odj3z=pE6M+|4xw4K zO-4$ZC+;7BX12mi#N9fUy*_c>0|Vp%y%=H@qXe`a8LsLsvwSN^yZPEMYjKv8(e3&) z5-A8nD{KFy=hzV!y*Ty(o|v11{61TAZLnHO@;d*@@C(S~aJ};X1#+Zd8pKOBi8^UI z|81%eu?IVk63DP?e$XHKtX!&VXIw#l8oV>gAg^S^u`P{(#QFY-&le)&C;&re2ebGw z;OQS~8A;Z|jwC32{-hwf&+jqvS0_LO&Wh?&iGdKr|$*Q2UqgMrSPdp zxo)dn(ytv|2W`Mo5c0SxJl-xK9~0xG|8IZq;@1dlkG)gPhd%nNI^YUQ!R!oG^o?OM zzu=A@CH<;f1H8_hNOw8omE10%0yZ`_rm7B{5t-JsP4fU8B^+=CzBDL*y_&Tw#7Kga zU-(ZLg#3!KGW9|%INB1vr}|XEAWcG7a}iwr6crT(1q51tcS#ybD0&1|l|<6X&riU& zWVI4z0ujI$v|Z)~hSmiih>+?L!Cvvow+yH}Uzg_Npg&{wA|oSJfyD0I!(Xj;4rcxQ ze0-)i+;Go-*%>6|Q|uhEV53V)TAQPy$a>w&$y(QEnqC@LfWPCkX6Bq#K^lVR%Va># z=}~WRzds{fp3~6m)Ok)hD7#p%6a**F`b_! zIhuo$L6FWmYQoMyvM_W{a+h(~Hc3K>z_Df$_EtIZ*C2-ZPbmH=@#sH zw&N>^n~5SFxm0KdNpA*kLtN<909KLu)9v+S)sPmnjZ18$&JKC}s#eQ<1@7>FFXIqY z44k#&_Fq1-M0I(HSNB;SZ_VRl?k`r<)-Top0Y;{?Qn><@8gE8C*5F?E zU2CNpfmSPEv#QdbDeYno@-hvUdy$=6zS74HnjM6y4vWrW1c)d;6p zc}|$#ySx7hY+=J_B^M2N1izFHt_XGi-nM2uXYBd)NLQ`Ge8vBbDw^i-Hw@%|A;raw z$(mD%n|YWo$H&JW5+W6>Cy`#Y`s91sZEy`9xqZ+dFjm!kyU5W@scsD$f+i;5?tr!$ z8TAkse*%WL32flPQuJN5=543TVI6H2>K@)q3+Hh+Lw|M7hvF-Ix!LO?&iz{0%bIIE zMK#~NvX?H{kU#9@J>3yY!H8PiZ@MTjVZk5N`;nXNLXy>3mzs62gT@l+m+dIz(j~_H z*x+XV&Z{Yr-(nv9pnigM*?*lunPLw|$_V>OHUFc&Iu7308pqgJyUhLo73#}^uXo(iM4Oiyab$^NFS2@n z)x#dJXD^q{E?^s*yC;v(G_a9cVcaCUl|#;5iQhy$N99`A`Idv1e27nC=6bwx@5ks6 znyB8TB}f4nn3%x*UbHZ~MG#;wKiF=~$7E`rgtie$*J6f6!Q=G`kTCp$^aet@))V`e z7#`-Ub&%cXP6RaY;hHHd*Fuh@REZZXa!^=fV>{*r_{1Z5(~w0Jjydb%l=1LZ0QxJQ zL*ebxDd)gc1ylk_y)?D<;lwo=`mf7>1zse)?WjN?7K}5^2hBV z<0Fr}%FIJDm^!(?K%7xZeNQR3OVA5A}gbkgT;4kwfHNIc_f{F3ywMPsPGJr1dbCOXPhH z?3B-LGN&^Kj|6?^paJz1X2R8e!TUFm2s{SjwX2|TX|szlwVVM!pS$Ky3bF5T*wfzo zb?HH`v~;g_UOjMR*oA>vak^W07qX=P68_m1wzEUV5D{SbNsq`jt`PE!9Y+Q+@Un5D zR+9VJbFW)(G5%@P;UT~a@RqvxGku3tVCCJ45ow_G-H6$s>KkLkmnnK6jh}ZA?jv0X)ggHGt9WE*kis}lK zMAS+=t!By$$oD}$mnkH}5LxHVX}U)Is~l-++99BpUZe}j zDd7=>YY`Q=OF(e;^f2m1b1VNq+$lg=K%*H%UuzLN;YaiZ)zIq|*(72qnL63~1t&$c zUVRAWd?k3hUsE)%N=pzz$05;-5=CYDy!)%eT)fYt!Y~m}Uskx_KwqhlbeNe@daESsbfPY1^&Vly}-i9(}xq$f(o| zXimm~czM}g_u;j6yfo(XZo6ujG4h?VN$pGMrTlRH* zmVpm_>J0yEY;-L+ckAE;(mZ7;3S?2ocxv~;{%rcd@9`D#4+*>vW6cV8=pQl%39DbH ziyFH7I~&(~{!c-de|~L!y?IY75U$tGZ8afj{T{xI*NJf21zJimI1wWoiuai@|0h=D`?)dk$DSzPDd!KC7{F8v#E)JDansNCs)n9&@HSR*9XLE5eI62lE z9eR~<9cquE5He#RA9{Wu|BA@T1tzTssWc06Z|NR+=nu>J8*X=IJf7_>&OkzdWgte8 zD}4YD-PZfN)?aD4c+7R$wQEX)_(76Q>4=x7CrLl7q;F;VTQT}?r$!XIdT*S*DVwAh!c+aK6(X+9pi+jY zyq}>YRqtum+|^-PfeDwON_tMG@AcZ9T=B$p!P}31)4^cinz0m|ZXAT$_$s0Bp-R<+ZGt~8MOagFQ)8D5{j%$t+UobJrP}{ z=*5JxT#w|m)|0>#g;M#zlX8Ev6a-*hL=o()XmI!Bw@@KgB}U*kQSI{F6nCOZrM`ru zD$tI*Oi9*U=A$um-kqSknR91FHG>~j*D5Rp$@96$LzW%r$_kBOE z%Y46Iuk$?4<9MEHp)CvkQ(Wf`3Zz)n;qkjw5js@#<;$0$b3%4{>z?o1LZ7p$JbKVj zFu8NK2dkR1;IBwGKJ=A(d(~RW%}j# zb6y~6^!2OU{k1zUw8@Q24`iqihgYhmXA!x@0|136iF_q5O1%0&+O6Gxs6Ir$L!6p3 zZ($1}6)Z(Kl?{GbRM}_-GaullVHS=W`ADH{Kn8S3Dodz_pEv>mD0N(2K@e&B1+7Ur zDrekK$XbGxGkIT}%KcQRi8nOaQ_zk7gzU`2FmlwR&5D)MpD$f#;3{+$ye~cIC8Evm zkETi^K!Ax7Y&ksgAQiPd{`Wy}9-%@ceJZ1aUPol$wwLSjM&;${RSxrWMB~(lo?=R3 z^a@{r$@o+wKn>l_ZXL83bSj$>JN$oF`3L`V8}wu(&POYjbSSj;dEs)d^v<(Z_z*vX z_zZ$74<}1ZL~vM}FZkb^_`;*8Y~-dYM7OIJsUc0i{S9+RDfHUd(_XMWE=b>RR9&Cr zy}7Yb@Pc3pZQ=3Xoj(YF>PlL#M?Xk?;(lI`)vYyjR;yaU&{2;ll$mt>x4o!p2zN`FCCQu(7pL4u-NQV7m2T~HGbIvba>JtxleEuO+n3*6-txOj(6x@|| z{rU}WQjdY#t&blgdDniq_`gcg^WF?SLNr-V?%$stv%sDBypYB8Hv$qffdzaQ{~+7% zsL|0|et&!iIauG8=&H(hckbK?YKaI5@pf5esS*^LUi_~1D|vUiReZ6jQQHMZji~E` zbB_d~b+4GSL|~wkQ1JWHD``X925t_HXZYqAml6(S6XGcd@Cny{J&qq&^F{lly#{T zFFu_!%|con;bReI_k*`PXF@mEMAOJfViStiZc6kcHibO2Q08=9nUBfa;aU%v-FHUk zR?$qbwE60>+lFtPe=K`mSatOCkMaPk9k;&HZ^1`VvI@ZEY!WL%u1DmIn*D=`O{jmV zs6zabCM;A{^PAp*75A@m)P=-rzW$0@H06~if$k{~25C{Y_AmT|M}eD=%Iy%#=<^y9 z;-tpsg9V^YB1=TixU+ru9eTL&@s5Z~o=$H<0@;4k(eg@K96CfRkpYW=afNllezga2 zH>&+A!zdynZjJlrbM;*=VaiR;%!Ne&UUQ*u@Q|VM2x{57rnB8Au+aP}dl-f*308G& z?Zto`Gf~f1>N~!%TE|*xuD+O=ahJ(7dkcwL8X7D{u-9id zN?wu>4?Ia=Okqw;B)vm%Ui;CDpq4ITY@a5PS~Fq;lo`uww(Oy&9WhTsK5y#VwQp?p zCF}`2oGkcysa!L(F_&NigYW7CIb&icG)Wu3h(>X7@d6m3Z5tity@z$9+Sa4~XQQ+o ze!RJO8BU}`ovoRLfqY!y(^Be$6V0|TWD{cBLp666pY484QTi#vmr2*xdHvPS$jN)3 zgVo_f)#Ik#kLP5ztUhp)LQ*6{;v%$nmc7cNA~FQlt&rlwTSXXC-iBEe)qspphMNP~ zQX|U#A+Bjfrbg?5+Spd^v{(^SO@0=$1k2ougyEo&kf%oz{XO|uBbl2-h>6Kk_rZ90 z`rAZ`qf(T#(r>)4FZrp*jJ3?j$0qwago5qPciOIOZJxgRg6gkdqr)_7|ALZ_bEv!6 zKGp0TDLrC?s2b1T^tyfL{J%VZ@i#rVNuFAysos}Lr0ccrzRBve$@e~D3xm$)7FAM* z`VPruM+*Hl3Cg~1=Wmm1{>rPm+wArJdOI@n-)_AM+~wMEdMzD?2Ypb`g?6pu!V~#D*`*#fNBrbzl)*qRU+_nxm-D zJpZ+jC;EQB+ihWCVSENXsx(4^$zY{!;@IA3yAs;Wk8(oH>ZXreUYx74B+Ia?wUgmr zaG(=$oojc4-DZ#7Dm9%~sRVn<#~19MO0|g3OOW%A( z40@K*m<2R9=44p3@#gv}7rpo(oolAR`Q_Ii*bsXdDLje*2WtEHZ#{iVi85 zAKup_pO3PbKJcFcr_Zrtj#o@`WB2VkvG;7c3ssPU;eJPn{Y+{qDwqxg@?t*VA(kC& zJ(T-yZYhLD5I1ebaE3r8gH6@(%13*xPWlb#ji3z^b~QK!CKJM)92IPj<{KonG}aWizobq+H9ttu^~fM_KNz1_bB z>=X__XZgN4yVZA!#h*|E^s2TBB|H;I9Q^{T>qD}FwDWz>nJA+5Buz-8i1)dpO2A#X z*jIctnRd#E-6t?-rs>7AXYh}$RF5y8X>^EyAqnGZIJ?5KgeFMOtF*eGH(@v`;Ui;IF zobEdEnhfZ9FOcY5{q*m3@ld>>(oQ0nPaQ){??KGkAZ=f!1_0H4;ok~ zj_8|*v(ZxC7O|9S(_~q+sSQ4R_Hf-Rf#)x&ti%z8d(HU2vO6*;NMAk5>!zI)j&jvc zlasuWyRNa6QZ8;jYBHqC*~Rv6u>FImK=o|{9$OC&vU|iwO(QIPY_FuJE)~mB>(|<; zYiQtHN*Zq?$vmj=0RC;xg7rV!=sSw|Qn?hhgs!%&Mmdue{9LEB zhNfnvid&TI#o3LGjjpb^K^=RkT%m(J>M5`MDL=WnkP2qarpSRLd3h_Xh?|eMX0^AM zTJ!tk`J{MhVX;IYBl(=IT5S$TF$6Bte6sK(-#e+zroc}wrugK@1F+D31)X57o?*1K;*-}ehKMK~19G+s^7 zsj`#8Yw)oG$ya`UWI0#t-)gj1&Aa30g!#!%Tm8zVlT2oURjDrZV3XmNjk^a>K;@4Gi@MM}$#Ojq(F36VjmZ~avQs~tjICAUAO?EM{ z!250Wel)e+4gCqa9oNcd);1%o=7-EJEG=I)4Cyn!2n{T?DW54_yc|b*N@yVqzS9cSx7l1C1tO*ae3=+!PeZ$<{!j8LFT@Kg`nB8-7Hi- zpXDO?Gp9S4PJB+-%ZY3xTQ*ou)01A3l_(?wFq!>5_=@?7a{r-RyM2 z%Znmg<+)jdH=%N@Gw(z60(z~@6l4Y@r@Xghco^8C~k5L z@Ce*N2M_LfTm3YztoG`T(7Mtvvk0Y4nU$)#x)Aqp4E~9YLdcDEuXZ9;Gc+SW_q4UO ztt2TIa`Y3By={0YT_-^%6TFZ82fI3B;&z?lTq)`&oM-QpfA9``92FJl>?SN*1g{Dn zCW%bMXf-6q28yo^77&*1+SCU6x+X9@g7E`Feh9aIaL~_{pH=(Qbjsb;b?11WS?SOLW#HBItGu9>JpkDEd$w50rPe1(xDih_qK#n;!DdghLP8BA}O;YzUhH*?D!#b^d_YB`Cw zzOjfd*C;En<;V>kR(=@x5l;+NEUAiZgPr$lwTecws-TF7!K3hXwzRf-1!D~B7^bI+ z|49?C|0Dbg!aX-wRNK>>ZoXbwQD9X`xFK*)LGfP_k>BsYj<3HT6{+xPZmnE1BdrV* z5ri$LU~y!d(KZ<>nY;eLaPHj zI)fpI`Of5@9GVG4cAfC8xa5D_7w=R02bJ9Bt;+wol$7t?C%tAP>6lKpyI9)jVGp(W zVh7n7J<$x$hXN;lR`5RM=Uj7Xan@E<)!j`aOZdRbDX(h}@pg3d?HhVREE>9iOFaEo zs%_%Zr-Ri$9&cl=wVZ#>6r32uc7ex=J%JO*FeP%I3ZNcN$Q)#5%gP5?Z@~jl-FKug z5TbOwGq!~p8fd!x{rx*T+VeaugqS+zj4yXv4*QZS71EveP%50i-w=>2V3@Uis-XYR z;+M|4*^@>U)&%3*zl)d{17wtxUnSVrr9JgPQkIDFA}%$v_m8rq_>2hN-^dyKSeb|Tsf8p4}yiFUq-_9!oL}f#~~>;!a(cp`~f2ainva@l=r!r z8=IS960Ai6T>Sk1;r%k6gZ6H>s#^rF+F74cS{uQOhY+}=V6+(w*riUDua}{` z+{(rc$mG2{cgSnVUwwrsZ6Qce%=7iGfy5&W$|S>dDx_0n-D3BWf=Yh!wY0CUu6A`% z)E8&(Q&R);57X)tS{PMylPUeCf)9_g?#llA{e=$TV@PEnt1rd&Nu`L!*-4KA^REK4 zDzA?$fz(C@_a@@6)7{||;uNEOb^#iL9dQvOGE+YFL$nPr|#+BJ9nc~9O2P# zQ&0XbUjC1BKPxSz^I#~bgoLD7=il)weL-Chu|0nP9+eOZpU}YvOmnaomz4>Q zwIn{z7mr=w7|Uqq-%p>z92fue^^Nx!5z15o%4}!UMV-WDSc_3M>1>F>-I?8@*?rI} z>ffAhd50b&mz+Tx$1st)M&x`22!Nm^I53R;LY+!YaZB~eJVbCe1-LW@=L;WI)W%NH zq$1b^*t*2ycDJOSx%oNRJiKn)t}fahiPtA3C$?VZ+>n)*N6Z!W0yJo(Ph#|=Z&)z~ z$?f-keff3ZKvqKKLvp$Vg{ zfHXSD*j)Rb$$5a6pTDkhZ{55Vof!<~GVJFNI-*8C=bNy4?!`!88$j{L)K2BH#l|>w z-HayMYBvS^3BBJ`CnqNds+yN~E!Wx9D$rmw01;gnNuPDi{DD)tGo79^X4#Zx^WQh~ z^74QM7#_|v{~6^PZ$Z|>JIl!{n#7SUlkl$9O-Sxo{-^8U4vxT)G#38oLU=gs*>oD| z9~Z8T=&8of=4l#`1LBOU)J;M6UF;VcrN&`jQ~)^P$h#DB^=&m(RWr`1m8;JRUpisy z)NCGOWg;EOniV3JDm%@SWLL>&HyGPKJwJ~x06$Pdd6=9CUcu|uOKP5;<971oS4=s! zF(|_WGkNljf3qR#-;mR%J0nIt8f9c;kROBbY-8N*zp(&zcBYVyEA#xc@2~WEO&fM< z6I=QRTbCNoJu2y(S^2ZwE9iB!UDxTbE*(hJN|P+rZ&3=atbjB99_j+=jHx@1BG_;$ zD(uOIoUR$IzH7-6Rw>kK0a>7;v3lifemODyLMR&Wnc%?HP}qcEz=g~tAzJz=H3YG~ zmoJ$wm%@bfXKmqtkmy5FbT<;L`uY^ycu)#{MfWJguo*E;X|FSfN|K%|J1J}fcP(>T z!HhKPMQ|S1s3-ahx($>Iii$w^6`=fL5BHa@hxu5PoW>P~9~#L8+~gm#dgeBvg!Rj$ zzAngGf{u)x42+C>boQSh>tpysn^z_5e;`IfQT7I>% zz5S?nf2AV%@9*J{qIKOuL+>LVn z>JeLA{!`@vk(M@I=<16RdwyPCMg1P`-NeuTY!*k-{15R7mbwMEI1jS7FtQ$$Nb7Aa zmq>ou_ZSe*J$v?O-UpOmZq75@q<;s+4Q5;TICpL!AdjOqlRfZy)lo_jy9QA&>4xdj z??`*8@?63n3>!4gJj6s^dhg0Du-8#>;Mca_m;}i^MEq*>M*a7?hWh#@@snBDvl^*p zqijfRZa)W_oig_}tOD0xNu9Vdc@o~ji0iPu6*NqqJZNDdD<@Z8^U_y2?uD*^f`-Px zmSuJRZaM;@nUdWP1C+U~9$8IT#R_|pC4=Alj`o81w~+8~?g@)-4FO5Bsx1Fn@tfq= z?##R#2{`=$(|hED+~0J0G&VQyb$$~42L4{0myZ~nd{p-kV)Y0jPry^kcnRMcgWRCT zx3O?UD(TB8+5hj^Hgi#Q6jBJ2+T98maH4$cs^v^_x7I z!aIooCo31>+t4m=<)r-mn_{~jR$se@)+<0)DwwDuq}B-I4`=|t8OU;aK4T<@$&4`RVyO+ODzm_m4_TDkr9T6Fc)h4}?u%Tam#vHd~YC1Aq zHR#t`@10r08 zD8!RVl0`dO)A&>#CNrAsY)l8%WPnM@w)RjXV!7Z(_VX2`4qF1Seg;p_Dqu z-Mvax?T)GTI+-RCqJOa$qEm-SnEXKhHtpP=R?*vIGL58`N%pt1yss&G69u`xKL7D! z?;`zLaiW?sP4r+A6WvfB-sjrFtU?#&M@;*#i{AXuy(d-$Aus3yp)D@t)==HyeUR8-gx+M*J=r=Mi;X_86)X8 zw$#keFYVn4uvX{xZ{vu^HEScO%WGw(N0gvaJEp{uGbSEFMqe#Jtu#q_oi78pifi7QNZ5;X7isEcyqVv&Z*PL_HijyZfZgKq< z^Nky%FBRV-IWHF^02ornFKmZF@U5`0<>ABZqf0fRkPIsDZL}9H0+tyR6cbe8QqRJ- zG%k=_PI>?FW8mw}|3jN;%lht6v;z$z3D2}Cp39HHMJX0z6qo4ScUjr78YOlzp+c{( zht>-xY|F}TAf$|c)GBq1?Tu<>e12_{PIZf*CFm>FbiF z_Lw|*7W&7@lehy&CH9nvTqgI??{jlGK!We?z<~p)zDdRqK$jPAu$e5ulTLgCj|&#p zTpP*Q3_BFawPdjH7e6tOfx0FYA$a@HZcN!c(4?N!>N72%;XR8uGW1Y*Egv-L^m#N6 zK7T&jte-CPPtKhG=O0U3@os-5&?wkVn44sp7?#8%&Z(H) zhc}_ro?Rqm@AGO+E-f{+=*v{Ek124%Xf*R{g$PXKcUHp;YE6X=krU(_`pNp6Bt(Wg z`m9FORyC0YbG60 zgN(65{GS9W9BfS!%TtkmwK|EZLW*=ERUI}LpL)QR#!L3cS?=1fZ<55YZ*X{cIP4j_ z6x`Uebd~kTf^Wn;G?n7gY{{?yz9$-GP1J6lAVF&K_8yIG}2!#wfpc9oB?c&X>4q# zFs*-sZ8WL2G>INE?4ADn)Nzf3(}ee0Gx=|^U*~@eZ$9rHbW-=ZRtRS^z18Tb1%+j}rXE^I~1io8eSGF7q zwIJgm^1{@7w=CH(#;W~|9>4HKp~_g0TE6Susuv6!rq@l3r>?S+VCUd~0}5xdk@(>XxD60kWK(xS@a*YVt82=MS6G zB@AY*BzxEoy3$Uu7soz}3tj`csQmH+7g@&$vQM3d(2SwgCsMDhxy7v-nYi?xNd8cx zVI?cn^bii9$$vO804Wn5j^x}($9e~-B>`lMI$aF_KPXX+*wW z(&{6@U;164VsjG?FS>z^hpJjua_$4RyE*9+ka>kIKN9xGN*`4;p)HZcz2 z*gT7345L5QF}J$1ci}PA8-r>e^UEe8_V)JpJO17I_V=Y)R>}R+)RE1@KG~I=Clzdn zbkPExOFw>ZZ=a8v2Dc42KyhYK?A;_s3+&%WGQBnuXKH5P@PSw^_(H)B+J{2ylg-85 zc~?o!Ag|NXqE>XgYjY_IzaKpIw<_7*^Gv1d?5MJ*p!sgv2mKKdeeAlSBW_*LIkB5g zY3XRO6vbPweWi!&eK)uN)vtC^n7054)84LkZ=%`c_%D-`_4f*2SDxyPS=ROOlq6qq zK1uG5S)i{goVnA}QTmtylfCD)kl&!%E0Cd?n%CKBFsg>H$CpkOtGBUPDpsS4uhwBg zdn{fbXHEB_@oW_=&krXbxybV{eE&VBP5v1*D~zVD+n){>OkBNk1zfy-5lS<9v;XAi zhImW_7`ck|OOAAAA6tb$iK7SRY&Vh|vv0<1M&_S>{ee1$2;ZwalDW+qZPrm93orN6wqNw}`2 zcEx%fbAfvk+jXwn{_gIi5AGXRsA+41LDo32yh8AMt+2bnL1rdUl{r~qcOq#gGjVL) zv09E8Qu~ZjPgP56d*{zLu2+hVm1-Yul|ChWgvP+Y7Xm6);y}mFBUsrhuK1cwh+w** zyS8)4M6L_%k|x&aOa!Z26ve(b6i1*3n;gZmxwyC-j~<;IAODTdsK*M@fu@0+oD{jg z-q+QJCTS?@ul%3x9A-5d;t<<|_Gl*10Ym;m+T-l@;wJJZRBjTgu1j1jxB$ovplAU4 zAfBnla|xhV1u0-3Pf;1~>T+vs;KE2<>0BVP?i*b5!-BY1Ax?CPQ0w2DVC(z(_FVLB z+iHlkQm0K0*^L!je2S#52eox>RoAQR10o4XhE*6Z(}g3m3@D=nnSsUV!SxsBVG09< z;HqVvxntny_5840J{-LwLN!7Q?J^0pyrUOC92iMl9qsQ&r;5l}goqhS*aORdeF~GD zM@^ftJ-Bz#|@7g z$3^K=-bfxXhx=Dk^NVSMSdJLaY2yREuXJxMF%|4P6+P`e9&k5Z`!_Wk&&C?gS1<&H zIjU?5`@DSrt-$!TTU*{+dz*X2@V78gM#@8FA>)bSq3BHg2k0?j;o`)uFRSPotLGUX zK7H~B`O!*)lMW*^kOii;zn)cSQSEYe)IDZQ432zz7%B$IU8Xp%SG0Zkck}%Oz6D&k zpbb)niwhta7gaK*Nk#OY4K&ZFW8Iz}Q9%`8vbEH9H%{zs@#(o0Hy4w3XRuU=h9pF3 zH%VcIwgH8rmX<}GVe$kn$68?y+1A@FRKOPUs}K8ffJ^>qa^9LLRk z%^V3PB4T2ZOC2kr=2bSt%cw?gs_L8$4J`=cvE*18#CVB34qgel{KBA>5`^z;K$SS0 zK_9KssLGrtEKl-++?c9Ot)NfPW2RGKyfVL9r0B{O(V*1y^gr7d8svU6t{A*tBQ+c@ zBE2?qg~ayq-_Q7M0tXRWf_DLkL>+vONJ{xCBSWy4o8CbPuThfVGoRD2=Q`2F zWir22XD&p3&&w-;ooe`8-K+B7-Jz6k-+cQ8#99NQ%)oyxq!2?foD0i1)_aeng!0G! z0zJics@Scnk&pO}^78XDNp$mc4{!c>FegjWvQvdCtXkK- zBR==aEC1zRf3|#Ud`Y^JUcWtUU?Rfta)ivcBbw?O9uCoi);&WpiXV;Mk(;(HpFv2{ zT3kO%=Tg{n=Y#v-2xUh2Ef$GaV($krmTFBC(NjNM*W zlwXK|v6Evi=6*e+*8RDy*ANk_7aCcq{1`Z5M+h!toBYq|%RQ}ZHziP9A}i55;<3f% zcGY?z$|CAKxj&HMkXxY~2C8l$BYXAg)%4WVsNz0vp_`%;@2r+Q#wnI_SB36RB83g_ z-s#Rif6iTR!+-p~ec?$uK`hfG>Q=iOlJ>|5RbfwHRV!vpvs2jW3N2UV$ITlzrcdw6 zXXoS-ufiDRwSY}$c6i@wYHyPla7x0y*8nvTQ4-as#2ke?vkrmZdlSBY|K27$YFgSB zEH_m9RrECP{MZ%C4^TRTf?j$wLJyqfH}Dg%*chGuPm|+4eNW8$kkC-hNZt}k-J57> zA5K!AYU>_y7c^|=qTb=)5x7A*y*>1MmapuUKU#YX)@J3k4;lnPg>nXhNY*b}$#i*V zhtFPmltV%q8WOG={Qqs?SZkFiUAp&{++JdZv@S6B{Iv0J!=jYf7>-G>hW}o#6`+Q( zlZ)~oa}eXTo3gSDDTYEwz?N-znu309A;QdK6@-WlF(E}w z(RTJx))$nblY1{#;sU9EgEE7Wfq|W!9U<}tS4xMNh#2)hW=`g&!EsNceG090QT@6T zf=TGecjL)0YSE|lF{94LdZ)(c?s(Nrjqi+kjmkfySWSSSkLl*aNeI_l5}&;7yE0{# z#zRG81R&Xj&aTwnyL(sSPFowcU!ZdDD*+IuL3L%VN~MiD-X`vRDV>$h?2;JI19r+uJmC^2E^(0~zpoQ3+Y$~0d+Mb+Z`bq*6-4`TU zp(M*tcBGMeN2)q}Q03Q34_HlHhqBwV3NExh4~Xcxg%4L)a${6r)}GL zYu?cl43xRCg#(0zG_5HP58|kl`O!Cu4IZnlt{P4N5b0 zdSH-*tvl0XtaJEYh!oflo8AM!8Kn5hNy<}%6G9>SWQE1WU4w?#Mkr(3&BeYW7y%LG z$&XJ>^Lbw_ihaxa?R zZ+AoRtGw^YqetGB9hZUwDg5K*pKQJKQd(8>K9g%)u?fFQ=^Nb>#&Y}j86IwTOVsE; z!$uG*mc7JVPVJ~?)Ty_ZASz3whU1Tn%c@u_e4Sc?!mkS}tg+QuqfNJ3H=+ZUS468% zo~;;0wWxf@jR#YRZ!^uOavIP1(36q`VU4SXz3K5@M@18=wjW@_1*JWmVEVT_;be>D zHQ`7YqRfV^Bt&)vn^2gTBb7eqeNiw99HFE2Zx#g01*i)y>Cp&~=pU@u3Efx`ye#@J z(y(-GR0zLE^Mh&?%Dm>^YV=X-ZW|6pN#6mC>M`k15yZ=XTR#AKC;x_`8TWZ5zp)q- zSv}*Y@ZYR1xc6M0ox5zK84Ar>Y--zFOKGc*lWUP39z7u4Rwzik(7zCb4ZG9&qC^V! zfB?GN{7l%+yZRIt^_7d}*C@Ul`u?+J-_XH53PkG%<|4l3?=S~Gf6l-Y7)W~wRH!UL ziTXExK(COwTV|Lg9kr%6ziI8Zjg5J%ver?i|=N+(_eVC z_d0C}^$nwwQcIMy-p}_2sE_-l(r)lr^o;>|wG{jyR1=GeAr6Aly%w-R&SOP5L2G)q zhnV)>s$w|s@kd&BqYlr?dsOkbbeggU`K?^r*vS@fg4IYS5zT2RfEe$%M_$u)SeHm) z{EIez7DHx>caRJS+`X35ntgwIv62?&7?vt#-PPY?tg5QI@^5u577TevzL=wAo6~@S zwn4N(4^Ys9*N}kWk!c9%cMR096iE#* zR6@_eNrya{I!!B~_AGEwzDk8yZCR>Pl{Ub6u%}>lj<`!cqwArY_D5vcHThuv^}A%-JB4kVP^L(>U4)k(9?PL8 z=wR-~sK*)qM@arMYbqUWvbJh~`jx!g?MQ8tk+;($;+x6Pl23`#Rl^!|WO#wr`insCseQmj7dL~Ei4}LGHx!Dn%PTb_xq9B# zjEt5ohYSQHF95shMm`$aA6G1nF-5TN>Iim$v1fz=>pa_I8KsnMy{C<_?jUqstvyD{ zD~xoy6~kDh(doK1%aO6Yv(cl|Um;y!k2`KcDZR%)%j9(_Sw`B6g3~h-v}T05xcl+% z4B1t%#$HAYc9Y9~HPNA5U6F){d5!z~H(ZBlzbr3%D{&!ikj7BTo^e`WOr^BSFlB9H z)w5za^xQejmkPEzmn$3Mr*}D+Id@rP^vBNRNL++pzqg;XoFZpcMY$R;CjROGUclZ2 zT__+%e@80z*8h0{wuNmiqH-V$fOLy~p>fYyRUXyvfPCKGvqz$Mm#TEYX~Md8E#nYWaXsSnnNwaMzy9Oi{)u>;mPT*JpI}Hw zB;DnkgXU9rn>0k%aV&|vF+q&g9Fm<9zfrA8Znxd5g&lvILgas7LQ08{h2Y?QC?WqRvb#yYde7Ai%Hw?$R>Cw)X{0G=qp)(y{F; z@cLUZGF|ne6_TVmNORrOtrKgqiP)CAGpc8eP=HR%rmH(USeoWMekzio(282 z+u&)BeB8n}3l>*jUmsj1ai7h_;A=4Haa$rO!{3XZJ0|jib$CkoMmrUMl%Ha*4VG>G zWvIltvhrwF9oslu2+azW{538Otbw;gnmnAL$rezqsp|S+BOW$>L`1!M?Z5X*6C>?^ z65JBF@8M$h-)rD$n3p7t;%o84hfSK)B-9hYCgu_hs-$mb%yedtL%-k>&Y0YMRxZ&! zcWO%un3xBYJAm0>{}RG@F%vJ&PEAgFA8iLYOIF-K$oQ~1xh(s@`uaN9T^o0bgS;+4 zytx{&M)Oz{_Q+oPX_o#d4T*({{ps4<>$djn8dsjfyk2NdpZn+M&%F1aK7G1)6vXaq z>>ziR(}~>SX(KC6NJBR|izJiP6G*c;q`$G&$)wImM(~KwYk8 zHjvWTp*0K+hAjwi*5L4IMLOHU)9v=LZ_PrZ?&^JObLmCXG|zwkkCnc)`pRqvC2p`$KsbqWqu4}Ko z)?4&V_4_wj#ff9YMl`{0N)N9}ql=n4I*5s5HxcH;AW2@hP$G1tLSSHMntQ1B(RuuK ze&(6bEFvv~ z9rLI5n}}skv%C|GV;Av>AS+|Rm%lghtQCoH2>i@xVWJp%@i5@Mm>aYx@p!H~)A#7G zDIbUKgz`Pth}HOKKCxnSM8-oGPMykRU@h4MUgD;G#kJUOBhTIsaV{?dRNDI`?eMRI zNx$U5Nyj1!tRk#BSipR5!uzS}QXgKx&$OP6Z%0Q*S5^XR?aKYDi(2mAZ~NKit86dD z=KnB2W!GoyZ^jfGb873L;S9(=xSJ?N3~3u0@>Pc4hnn0jYp3~@KML26 zAN>@plmy+A`y%~8uDfudLMQTyyd_^Z z4NUDJ%?!C@KAW7$lT=VX z8Z`qAqcb+1e}`E>M<+5Y%n|A8Y$p9A2UahDJDlYpDHj%U&0i^PF5XScs4GvhE2kcV z41g}M^(R;mLWtXy70C-*t34Ty@F$UB7oK~`?dMq0Uj7lj8rq7TG0YUF1QTpmXSgvOfw6vp&9vqv)>%J(NRkkG9B!(d4$hmT5q&3Q z)BO}Uarfw+hZR{U&f7|QNME^L^L!55N-O7+*wPqITmob^dpJ8D^9}El%pLxMIQS)5 z32k)rH$|VYWPo<**{UD)!9n;)ua^?y3hn=&yEfGtb~-F9`QGn;+&0$R@L;}w6x&WF zNxQ@0Ku$#ATIEvjdi3btLUY_oIiyOR$>UfIKBh$Kr*^35-3iI6kod|A@%rt&Nl!hx zhH>$I36;p=bV@-0U-X!4cAt#AJoImg%ji1zu+@t-0mxrauNoc9>t0>>HF@^!1n8Pq zUjS|buIN?!C$@vPX8E@_j`U>(!7xFnMl2B*$|+=@1_9Be2D7)ak63GsJa3Q#@u|bmQEdd_ToK5x%k=^4=ubYQu~*Fjf{-6 z!9&YDtXxU1DH3(tmsj=7=p5#DaQN>}`~uU;zvSDOGY@M{Oz)~3@pE#jvQg8}04WTI z{OYD>t8O<{NX11^i!l-vIMd>6DS9)h(!p!CtAP5Ua^V%3eV+#1@uvJpwW%#GDuTxr z(k-YSWqZ7r_ie^?*m;>glo^)IAHWh#kzWRMQL+^08H}*>_uT62znlmT45TUGlXG;$ z>}KuHw#W{)bzib;cA}IoJArWCLQojSq5yhfQ zfZQ0QfRP*XTTFj$FU-u$fH44#-}4N4M@JHelOGTO#l_0pX?DB{qcs+keUg_2OU+(B zSGq(}W%!a|(6iB@fq*plIe>r$^5ti2uneiPZtZ6@zvRu}0{rz$Bf2D@EJpM-6?){Q ziNh28XV+pqME6|x%1E~*6wPJkrCXITp0pOd*z7B!p+_w6GYD`CjaiV1l#H+df1f?^ zluZ3aere%iEN1BFg&A<6`n*K`fYJz8G5sPi?qv)Wi__@- zzutR2tQmGez2<-tRFe%KFr`6ZU?*j*-!A%geB48vR2?NSnVXb&Pyt;|V_WQW1M8%r zjEaWdb<|qO8Loo{1-M`Hdp%Wo7Z$__c;=A z$7>|2Leyk(>u(ov)F0H@9$h~3_alIZpX%ztCHQ#n)yqXelO`TVLwPtKQT(9CUb+A~ z6ath*r@CJ)pTWwY@Q4T?*A~8q-4}9p%9uFw(4vjddrJKZp5qlt<>-)r29H6X(^WR@ zI`xt_$uW#~KOs!-Q}>GbFP34!0r{^8T(U(6fy=kn$w58(J<}512VDoEvg2DaP$zD2 zq=WZ{wJeblqo(njA*o-C+`{LmM=TGxL_r=LI{qHi=q<$xy>3nbDpmW3tgfxdUsag7(BiS zP5m!_JlURjvK*`qX9P?mNFFe!$y~=PEdoIQ zCjzF4Xexoz+2n%Yr|sG%M{K3Ku*LZw%MLs{Y=1Ye{^fe}2VO!&Z5ef4$KG4afkoMkRzW^_2qCFvzdcz9rTk)rQoA<=eJ9}yLpkKH z+iyNswURu@jm|?Hr18N1@mK;|(I^()7EGfvLf^86`5t$%0qwCfANA^pMv907&Kv!H zdGcQ0b%{D`I;S8*&w6@S_qUpuh8rNSg8%a8+moJa86VkdZh-G`2U*`RN-XF$kr&_V zZ&vQK-^uD&vlC=qpoNOXjX%!n?9Az8rT_g9xs#Y2uE5#kbdzpHm-5d`u`eY}T5#0{ zQe&Hpu`CXF7!Xlt^wvWsg^HYh@b#USx;D41q(UmIBA{l=<)`dWJ4v|`Zc^(|G;vdq z6w6<2+Wv2{I z0nrwBl{D_KHi_z9RV1}Jz}5#ES!GFB%(UV4JcZg9~2-o(@14?vn9uR=wJ z;#+J*i8Dog%el&LQGp-7_zlJgTI+t`Mf{9gMGOFD1h>qZF2&lm3>p*n3sq`*pR z4HZw?N|(2m%2m1C_f<=OL;$XTlMuVhWMf@M$(!tCnpqXu{_d?@(x=eIty`j+<>ciN zyE73{Qd0v6#69!$6y`t=L|c+(niaQR`zix5*!tv2B-n5PdwVG}?6vq9%dO%nx9&`R z^E}y;*%4Z}UpZszZ_Pwf7b_hM7XMo+yuO7PYdj?m-=4m^$ktWa%rBzg%Oe8?<>f>8 zn`ibXw;*dyL6!YZZ+QIMuiBe*gwBo*{U=k$D5?JP25S_W3%N&YS>!^^(~SyN$L8+} ze>irrd)r!6l)?Fw=FjPdn38DA_DMW|FENdBY#mb7eHWQ2V^FTw81KY54QgguYASx? zx+1+rsy{rx#J^8YROR4y1hOhfZ zMDP@>>{s;+3k$;^q=CYi=aw4X7Hd|8mKBlxCYGrbTa=2K44qhWD4Qo0J9Qpoq>U^b zEW$V4Vr-rhX?DXSnc|vuX6wOeE(+JaJfCBCYPWo!aa$A z;y*85-M8M#X?N`HKHfKYZSrLOty?xa(}OxnN*#8joOzo1yClt|bMTVQv|}w@N6{7N zRXuY6i{Q?EONP>wo{cbdnv~lbX+AbMKBsWO33+>9fq6T28gslquroaHt|x{s3jDqmb>$|M7yCQ%%J zG6AxTn}N28ci-h{x)L)w-(t}NZXJim*0$J;ckxtQWLpp2-=uDtF7Tys>ug9sOM~e6 zaJ{#;cW7t`>FW6+^0#*;mOYBePac?ZYji-%0FDO6hdGi8Cw+TEH5E=Es5xZ$`1dF2 zFlCfz$w|}-1s4=J+c#ojFWm}qcmIDhy>}qi{r*30TxPP8>@NFCl9iM#J4w=#gpjNd zg=EicQe=~mlu<_6o5)IWXGe(2Zc)F7^ZkAL>zw=CCtTj|*X#Lw%&ZR!P{JTx?iJ+{ zc17f_!zA^2*qaqe?q$PJ&|T<~;b4Gn)y3Jl{wvj~Kw%o)Ic-{Pwu1Z6C>%m$nLi)0 zo?zKuTvS$6WFV}s&7aN`|8fL+b!qaJfNLHeKQZXR`U~c^vx0ONdS?0P&WstKQaYZS z&%O7?@{b*Tr;)wGY8H){g4iCxV4wg)QYwEmEQ3MWLVc>3u&!G;$j8SAdog%=co>S# zR^8N5`{{rW#mc^x(Sf;?Ta7}UQ-E8?(e~v3U%XX*Cf5iC05s*beivuV(N_ypixQ_* zhePb>(W#?G5~{+FBiIO&DBjs_v&TbYO-WgKx?N@H6yZ1WT3muD>ZoEs&O!G4(pFmg zwwv2pPK+{rXcKjyg}r@*7A}QM9jr~;`Fk5vEj#Aa3>A|0GQ0fP*ub88iFV@9#BG4C ze|X(kR_J6*&}bj7(R&Vu3R21yb9D6pwOD)q3D_N(q? zT6;8_>MgQS-pfGQT=3}3vj8Fo1GQ61pDnJN=6Xo_v6?Mmc+C!d_4@Ud3=y-0cUq=9 z{yQubpm;ioVs7t5t@)Gjg>~Y=H}O8Pvg+?Jf5DnFGJ;SXFy5t4pju)+LS}J9Nk6ZO ziR{+8*B0{D$YGlhmC7AJJwa3I5 zCEQC)q99QCqfiO72=(FnABgF3X{D&In5V`XgIEolvf_YV6LLk0X4(KyfOz8Xwg{|q z-hM^fWF_^>3lF(NU;Wrz=e1sqG%I21ecE{gQW6N4&hYtN0QF+|X<^4l1Gci~C!0KT zZ}?MdyjH7N85q^T^d*s2GK0r|&1bs!^KP2v2Vr-jng`6&# z&XG-0YC_zZ>?Ost@fR0#fuDqB13-+Vq;+SIL0(Em#_Wi>cT+Zdr`_YK%CZ4|*e{`u zwfhJkl<3LyuC6X~S?Gx2!1-B=P;##ttP;iPlOIZdFnXzE9x-7PAJW;<%87F$ci2Hs z^nK?)?F0ln>G~7nCqRc@k49HWjBPhg2)VuKIz{3+-&lR;5Bo!%0^@)aG~M((>?i)i z^?7{9Tq7<2`HHZqm_ol^dud(ayYN9xeF>nt{CZSbS$X4^H%BR!N1@_OmTH*K$*U3= zq+y5-2g8V{g<@|IK$=YHA`5S=2Hdn-Fyj%(c`o3oqSnyS)ja9O31@J?=)(F?B4Qyp zps{2|4Q`@oc~JwZv9tJ*@iC9JV8Q7@(q)nEQ`hWY+7$YRDvU#SNVyu16horaP9 z`UFLr}7*ef_tH*MZwkr{2pAg!dN zBqim4j_{O8tu%dixXxO3@l|>2$Y)kPJN=y5=6PN^%Zt~2MkzS?PBV@tMAA2ut#=R> zCG~7*Ee-Ny(hL$*(Kiq6ggR>Fq4g>l`TqSoqAS%X&dG_in!W@_Kuf?ISYS-?snMD} z?O56Hbf{`EK}lVCN5Reky0baEEVH;uRr3%0>LH)vcL?TDFAUZ_dCBB2q$!P;$?0}A z$Fn4USkP2KRPAjq0ngs7SjD?^m#k{MYLp@LK76>Mx>`TrPt)GN^(nWvey`(T9vnPF zWJZJf+~tCQwh9v+ncZBxylF{EShGDwfW(6fZrO7g`DVReZ|+JzqH4THgJ{6CZmP%! zPY!mHrAt#>`y9hu8|qbHSr1>K*84ohqM2Y=PRJZ@Ry>M?Fg|zuK*c!O&7YJ&qH|cttU)aKaoc*wAazcqb1D-z0;hof!(R&s< zkXWF9Z)LjxVIJ%_%#LD-(5k=BHY(83@)k)yIJ76y2}wy4A#YiWcD-nVI(&nZBPD9Qk zoyIdS7(If>A0X1ng!||}48hpAm*qp5F7UkE2KdkgwKRC{a@`z{QURnhDqEA}SQ&FQNOlLjGKKc0(tfRH-_~OYuULgjnYzQ2u*PmPee;%C6YY4il~!wIO;jLtn_moGLKFKv(Cj+|#u?|K54c+WNEb9iTHACVOXjyz{nxod1?n z@HX8LU}e`6j6Y=i6n|SiQXj9qRDIFV@Vfuo4l&JE3kJfG!{%Z*uuPq9-n`G6A|<1E zUzJ!~Uaqwe#FgO1!tBpLv921jNS9$dqYUYjr&I7)ddP2-K0M=`Jy~E3?JI5wy~B3C ze`%Yoi?_}MunN_cMMK=6LgEwvq`(r|m9>e=P$S6r-Rczl11^|4m`Z_hs*53m}N zpr!akn>RqG?|ti*cD>cY24o%e)DiC?mn3^W^yn7+z@2U{P11CPoCU6Qbqw%-h^0*` zYrIm8IpZW>g2@EEzL{zaf_S|YsT~m-H_lrrE-CHrWf5S+=PwdV8x~y}(*?rv@#Du( zPq45h#Q)^4w%K4mU-TaR?|Qi$5$YpZ>*!+vFjMz;{rSTB?+=%KYs$+%|M*jK6{_(A zo!?G1{GS#eQEys}OUWkJoA0NX#tt>+p)-)6;sL~AArR*QWuAIgeOZcsPuh#i5g~E( z{7A#jfs^3x!HgczC?J5b*u2@Pcnj&4Fj3&pxJ*@e^|yEA3EhE_Gs}HB+FL$*FJIsj z$Xrr&JyG%TYjrhVdXnFNQ3n}XLS5*_vhiIiXT-Q(I%}BYJJSbQ-4@70LbHmQGMvJSd&m@1V%`QZsrOCF}6`}i0houFI|%Dt9% z)oWs|lF^2$XAxZy5R^CY3le*J$9D+5v33i+PIK14V`6^-BggMX{>lBoa&mKp(+sdp zX%*ldq6^G37Aq+sbjqBJJm;5rfAz%ihBuV^YAP!;`zP3p{GEl(UAlUDkcF0cuAJr{ zXeY!Hgg$x=Q|M6oW!Hh7;Y@!hoji!l`26-=J}dm|%-u*E4=IQr;_s3fxV*jUD3>x9 z5yzswLF5!{%}E2k=n!w|b6oeh)Nu)I=bZTF9O65?g7`H!a6-NJ!T~Zgy9kH5jy*dF z6qUCU!ZXV!o3)zgZl&CO*vFdxtU9m)ag`=e-f13Fiwf=xa0~DB^Oik|QY0))$;y01>O%*1x&1uE+H< z9gC!6=&1_x<($39Oo*e5c+1nOj<`?$3Euin8?*egg?QU>c%2gLm5MV3DpoZW8^OtT zTBuJbaDQfUsBSwG@`G`dl34LU49uhT&-d@9r(6ovf1ha@r!v=x{nGOY2F4!)2#Brv z%_aTX%0_9^_+)sa-_6Vj&+G}7GZ8(-);9yZ?8#0I*cI_<{6#qvvX$aKEZ{zzO>8oM z;2SqDzW}ZhzZ@}Vtlcgyr=EYS1h)`E2cce^2<3-Rh`kSoe zbPxOehnATBEir*WTlc!->+6fS-=QvQI<^0Z5(*J-G=#&L&7R+~v{cU<*aFaxHrn=> zSDYNo-mIN+cke~N__99{=6vOaxU+;cEhwpCV)&z`Ell)UdA`|A$!rlsZERA$+0d!IfCpo+G4uArccW~Va!31rudJ#-|XV0DMcpOHb|Ma8E z@Z-Q|!BUlr*Mr5V&QNpj?w^;{y=TX^LRoge5)%_)4mwI6#?vn)1+5$mxU*wZ8##Xj zP~okkxCR|AH6bKZr06)`vo-Ra*ZL^PBVAY%bokg)qM0{3t}u^QZpTq$xdtvpLHfO0DD()GF)hLN6m$ zBBTTa+AE#1VbJXXM0{ekRxwl(K)$M8<~wtI;*4qq<%c8+JBy^Fk=Md+79THIwsu5V zEqa2`b3PN*m=dCDRCJcJ8hhXSy?`Zv0wZ#l325@f;=8PsucA`GTs}P}8@&VDgQ2Xn zbSa8a-Q1>~t zxqKM#S7(#nc6E{I1Lp>q1GSU26p1Wx=g40~*EG_aQ_1mGq(0ak|D121D{f&&rF;0K zjr4_B>t8eb$MzVa7Hbag#a7n(>f+9sN)Q7uG zj8S=f8nW^oruB!5mGwq3oj#lH*YxA%;|ob`c^5oSaU;EG%03?d)onA%5%nhpVn@-1}bp2XRua zZf^HP84fX-u=Ac!xu=s|cml&mwgT>o?#ck!JDd;%MEx#%zQYQF}+DL_#}NZ7@w zzQ=9cRp)TxjWt7QdW$7o6W0$F4tD-JT{t+++?=sv#}0H|$aW7i?_p>6YeINbk+>x3 z+ac$(BoPi-RuAj5lnKlf($B4s_U3xT>m87>ii*%>G25Mcw>CFZ^Qm_Q`wM?|Tv4}* zrTSm-L2-P`Vk6wWJw5+aUX3kQ75%CHr&3Q&%iOFb-dr2VUHP3=*v@_0qr>t;f>nHZ zf|;Aho~i}?f01TuwR@5~UUcg#l*kmMc{>ir3v|Q_>piO6MIa9#s(He0y8LQsuvo%VfgTICu8XyPWJmi!LAR24Xi6DEQUcI-ef6u$=1#iE# z(A}xfy*_iURpte?7wa3S)w*^RZL!WhdG+emdELjxVoH1`#n}TJB)Otw(`jEjjZ`j= z7Lt1&s+Jxn3m+sL%rdP-s@p;CulvRky-Vgtbu`q@w!EB|Ho#mi#dp0Bz{wN52XY@z zE3-rx8DJY5%9>yPv!xT=h(MRGzzJH12+wu~=a>YY^k=Cp9<j*)=@!RrUqb1uhi1OWERc4@ycZ6Wngy;iULkSO&Z84szbX`q2j zK1+evCYTMQofvs3LPgRHayF)(OeqP*C7h17OM6QDtQ6H=rTI2(7|IL zX7)ms;k2dwe#*{s9->iN3E0$=yDna@Ia4@T3x$`^8u6L2hGZGEa44_){o(ovZ>&tm zoT0L%p_BJ-(ty9CEuu-e}(S;#PL39%$_(Yu$UVPr|ND_yBX`4 zH!zJl{TToIb$q3A*uQgr^0#&y01-^GS)H(yU?EAl-M$y|-y;QS70R6)QCh8oP~@q8 zfi$jOvYyXQjuJr#*d0bLWQIjvgFO|>ocZcBmSB%R8;+0k{pj^~It%FU z=OB6hAs+z?693R>fAhUB!Gj^90pAi<-$57mg&(O3vnP(*SA2xxXZneTPq4Uut-q2F zhvc>|KjE#73z2>eOvoA5yS)z(`f4n82%)7m5<(YMKk?Xv7(>%Gb?4*o@T4#%g22GQ-1p`6Mg`N$9y)z+4U+aiF?|cE zI2c;@QD`ImneZ9eOlsLoE&lw`T*!f}q_46g?+s6c9Rlc*LEbJ?H+T2MGY0aJ1#o88 z*go?#RlH#%vG}_=k>UzIaV)&RCEYZ+cDvW)<%JdtKRpyc40CwKfj^v_;CX_~UCZzb zapsa{T`msK9vUB4k(DJFw7XI`(HIEFO1*^Uo;6;tG>u4${DLR(CY?ICUI;ha-VhW{ zJe9Z;iETL)0-saC=@sS22jq;d*tP=A^bcP7RSj!&m1+I+7h`OE>W-NmBTm~n$)vl! zo1gaLW`aQ&JUnPR+#C#~;UmAi`C6Ec3T2++xNwRM7WDTQe;%wG@2T#-#1Po6)Tpk& zrA*;7WFD#?+z`xl_W6f~j~tos^tpO^&M$k2v%i66YYYKm{*wO1HE6zh`Eoa+2bX{1 zYAbzYEav`a<0~?*yuBA!f3?$2I9^rNN@}$%g=9SA?^@o6XO}RK0z%z)LhO6Isl>nK z7+(kJ;uWot;rzG1HbzUv$Ze#!(YpJpPRoALi zD#Kxio7UFB)F%QSkUiBo_WI^mlk3-?>x*#n^5TKO<__^zelpzN5)Uw(&=R4ca9)ax zj?QkL8QrFak+t!h@}cSxLO5{N{P8MJDP$}r0*pWk4E`)>Ru1p$q8-R|`WY}{kD^2T zS>!`%^(maMcv+Do^t4Z$jlNRc@R5w}3#=|pli;g+ohLZot7j`9lov}17Ldi_&pp|B z=_x749Cd!fuf2={s88GYs3UGWat-J{zLtYB!*!KHEoK*%}S3 z&1qCc?}vx!PMxG7(gw6xk_&2m7QenFFl29I^BK>*hP8IiDS{9_LX7ONtoFvgSpX>s z-H6(OJ^v7*c#^#9;m0&SBtX!fp)|d9^{VLnqg3^jZan5dc;+7Kl2CDh95MCoS1UUCD4MZxNj`;p(Hz0W{RWup7)Fbw*iYtD2 zCIn|XvHf&aM9tImwD@!c=(vNFWy(ve_vr4~V_KHT|KQEcgos5P?YAo@71l1`!T7M? zc>Vf=Z>~pe&2n)0J*8#l@@wg~*=4`@?j4(-yFlC!I-L;d2p`a)_=z}fY-PghKj1)tHL@|-M ziLy=jx-kj=Oi(4(m)C(oED?+$cd>yW(|S@YR)oLG6cJ{(y}k9T>HhLPGd862;dwNd zsj;SyJ>^|BO6MA3WR#PUIp%v}r?QI5o`Prh=v_DLUOq%H&NM`fZu~D!g&`}+215kd zUJefEc$Pg(T~9#Ky6*uSj|^f=j##@*2YfQNh(zZ%?%<$^0+TwOWQNeDF7dhXN^u72 zV=$ZL8sQf=HwD>UM$d6p+LUgs68}-;fp4;Tr{YkktJ^!XzwEG><4@E-zgsX?&gnx0*@=&gA&vIBm-scS3`yp3_$MvCQJfJLVRlgj zcBeX1iNK0G=yWn(M$rn1EolxLaz4;+$|oLKj|?d3)0 z4|z6fmDhFqI&X_s2Bz+buZs(#tkc_iig4*jxs)PEw)$*=7DE(Q~1o%&`5`#-SL z=u36uF+VQ49&PfMHCeEuy=7AmN9t4Id}A>V$4S$;hpI8=Z_bg}K;*(Ze(toy0~ODe zpf@b~G2!8--*ABZ5T&KK)G|L`@Mb3d86W!r1e^-F3e}P2TmBLKFuSH)L;90rVTp&&dg7zxZB{ z+A@Xx*P;4!TjlxNkBqsTmDQMGL+0PSa1OWk*YtEgGAf_0>zDT90VT5@w0l`txF4|( zk+uimrj<(({{8a2c>nWd?s}uk)gEYuR?kA`jiuis8mNdLA8*qP-b1*BuyWBJdhyG- zMqsU1ouqvG8A^Fh77l6&usnFv;@!+d*Lhe`vHHb}7ig-W55_wedri8X%-!|*;5JiR zR$KdR^%o!&fjC;%EAK{tyD(>rTN|Kc@gp02Ltm(z<(RQ`RpZ)!^~z2<88U1l^G`Sd zX&piKsD8aCSM2J zg(|bYsG#K2lXvAbfl6EniI9IwU%a?ykOx)g`0ejXoA+T5Pqk82x!-?fQD}i^))Oq?w~Jvcv_nM!0%s{T1-+X=vZZoj8OAH!0gQ52p+G;LtA+D)%1og73F zlpxTO#>8=-%~t#Ue*#p;&`yu1&2eCRQU-{F6e*~0(+u`Rv_KQ0L}MM_1)?#iFJK|@ z{w{Yp=LJ}dJgW&uf2^->{r*sgU>B@Y&F~XlNGJ1sVp0@;b!cGV2D*8{1HmFX^WZX+ z2VhO&iv(%aMG`ECnTk6CR703EFd}g}S!Rkm#X_IY!4#AR#{e^}4*$)t2gyoO*YL$L z3Ak1z$qrYCEBK9w@%faTw~9M=G>s1j;;zFf-B!%5?uLy3-P?=0{thC)g4qlBWs4=s zWb0gULKr_Z;1t+-iQ^EDTSQq)vx}Ekrznh_<21|+{~mT&F}InPXR|OXxiV^;!g&-X zEcuYRw|QRUj{jc^Utg6(y=7gpuLHS83b`Fq=)S|Sf%Or~E1n@XQ7v_?0~&%8slf6( zg4i|$i=NULKygh=Kztwfd9rW1skteWW!eWiTa=WQ!7RKiDe<3nymsvx-US@fMHOQL zMzalpH2AM~?%d%RM&__2Lmw+NZQ`oGX#M#H_r8IFrYwJZuq1?LXHl_>)JpI;_47^TQn|s+y)SzZ0V8g#Qv^QJ z3uV*8pN+#XWn)C&@vOy~W+xwgu=!!nZILGh!{$)#d-^M82Va!~XMQ$n_j0>^%OeDj zA?E#uOk}2HCx<>Euyw;Xt5G~r&vdW%ET~KLRvD@Tu2QYUL7M7M@+z6xQS{ZD%pNrG z>(?K#b!_=C?8d{Fl#sNcdFl~0z{b_&$$T~fKI05Q%^P~9Z;=k6jE22v`SevNiNMod zhp!xNHwaL-O9wx-M#aU&A>XBo>c=)H{Z`?-pa0kI-+e1ADE`>^L2i!U2rSBXkWU{; zage`Qlpi4KOuX&cFASCRbl5L%a&-MDgva@h>a=;Y*fg6c1C za~%0@5TqEAiiN{8w8i!+p-)NQ2r^IfmbCk$Qzl(aIRmL>TC73whyexX>4kh`85NZ} zNM=VzyQ2bkN4Mo06+FB2I>dD?#&=WjxTL42ty?88+4D)gD*A?Rr)pglpeRDIdHY5C zx64=VJXph2^z#>nSLAAx^;YZUkM{al1z5vu|=3|QHNb`U>Q#zDKVEi#mxgSHan zCFnR3uJs%WUpkEf^Rmap$+Hcg*$ zK*L9l9659-chC+{vPc4pCOYHeje}k*Gt>5#WdA<)Q+u3xYUKTU_=V90RygPi|I_ZSe&N-Yt$Rk5r zB1=RIZU?_;$bx%jQETjR32Z6;e;mhiDG7Z+RqRjv56t7p-WN|#xN!V~|Iy%E=X3hj ze*8edS7K-Lgt&0sNSNna!x?CP9x3Wu!_pbSeAV}?~sn)SmjTkew^den{8=M z`AT(!ao-8sVvUzDgq$r{z2nzQ*^gOCsD{{AT_RKHqZ$zK?r*LIysq{z9FgQ2|WJcsZda+dke%Z15X(SV=$q zQ0e>cFBFVjY>~tpAea?rz6=)PRWC69BC7JMV><|=-KrEyc=#emKP{sj76OC+%#N~d zHdi*0siS5ADbfG^r>+vXvO94QB3GQJx<0)Yy(O8J)PZXM6Kx~uykB1CcS&lvyT+2O zz+uKy7l?1GX<(k++AWe0zUr^fa#+QUt8GV!8Z#k$EPK}S-Z2*a-TW76)zV0Jl{20j zlE@q%T%2E4f5W8r?O~rS=yp+)@5rTcqCT2nTo2=ntCYqq+K{AHm=^`&$a>5LDK15~ zSkA-M`$*j)jFzs5DKB}?r_D{CupNcSwKjjr^zQn(a}VB?2RaMfw|!BXnEWVqW7!dq z!H==q2U)}0XRt(BOL7D>Of)u9+b~}Z0A9iBcxwC523BHm4nQr*! zGp&D~W~hTb4`_qc1ZA#|eO>b#^?owX`2eEi&j#hGotekUxTmeDMvojl%Bsn7jXd4f zrUc6OWc}8j`VpNo<0B&kH|+);UPOni5ln?Kn>stk4$$4*X@m*WFdjq{h9#^7xguYm zlM?lKoa7Hyi-YEoHF6Wh$;ZP;nUJ2Ij>Hp2l&4md&vl*1$3k?Zj=3SCH#1(~E;K7^ibtqZ?rjHM*U(hWU`=yj;*L!ge zhQCXpDtOqiiui)!!71Y@C97{+LvAxnANm{wiMr#R=0$Z|7$WT4FJIn!m+I7ZG#mwD-H!C@H|(hQF}JwT*J6s=2zhU!Ze}V54`^?H zD0kdEZ|C>bzUbu*_6RDo76`O68*5f4)he@HC|j^&C+mxFFfcMOz^mYT5%L6O@oN@j zA-?-=6q!AT%WSnZiW}nyN*2ZMrc1a@It!}N42>m;M|{vbJF-(L=yOELTAe` zqyGo=5(YOKyvYW62_CJ+V(XxTcU3W%yg5NZ*R(Uxv+((IrX0ezb*&u!o9^y(2X+bZ zC~b)Z(8ybyV)NhU(nMUeOrvj9C!NtP2ycyvJ)(D50)kSh(2=~mcf33|g zzyEEe0DHKzEOOn&*)N)zQU9h)1@6If5AY&x3_mt!vs`m7@p@7!qBg(px~Y*QpQ&>1 z7#lT@QCJuRd30J0A#MCx=P9Yfsu^>TW@ep~sg~7kXK#-*M6@r*={$Y@Tt+xHO8e2i zM?0G)T3bVqBLtIBC?(%#$b)eqU?5Ljfz}OsB26Q`jp0#J5dIOcNoMbn#OhY*WCvH* zm6{)8S7iTeefLE}X&58dIj#>pS0A9-tN+7?J@mn*5_?WaDn+7-jfYX2$UO1rQ)=)$ zRg#uR$it*-yMEs=H&7c~fAXqPN*Wc-Tq4aZWpVMWRv;l;f8mqxEz-v7oSiM$rtNUgzG zeUUrLl263dE@JC&{EQY>EP^v!KMbdaeH zf)LMxXaY>+x}RY^zZ=_XE1MlVz0qSOjXs-1J>*kIMn?8JK~4eT=2Dz>*)Y_|5X`4b z)*n=rU=ExXo6dcRp9(qn_V%CFKqoG1tNaFk3vWME?6c}>_Xrl@0q5WU7f4_sZ^5fy z&}U2fgr|7uB8z+3rr+@P3+nF@3m3n)bp4*>=&;Y&+iai%<5FJMREGT<(S5BG*C+E8yaamtqAD%oIezB{_t+Mp%j9meG z4VhuT90vp071k2H8z?(GMWO7uGQI7e$=_<0Q9XL#=x9PitH$vRx zh2TB=l@mkuaw;mw{ho{uyA*JfsOfhx^)uOC&RM49S7!@rJXb}y0cB&aw4m--It**f%H7%nX`n@x8KN&MZh&UxQsn(_gnUpKN*1uqSn$7V2JJk|_cRfYk2k&3yZYXo+tN3xU%>&y0yiHnw>hnfApxbR!8NH{9)K*k`q#e^5v> z5IGny4vfr&Q}I^ht6o=Dj=9xe9(i#>f_2&|s`C_8)H`S0)M+y(XN4x+vJx`W=>jB` zm34`UUijmk|$$bR;CdWdV5g50A_7%zA8Z~vet zQ@I3Q54-z-zyYqBduI#pAO_1I51%k9(+RJ)Cr+nDbPE@ZI{B`C?#4GGLK^_{tnWW% zt{fPCc5R0d;WJa@+cxxg&=~D}H`Pwp!0~NNJTT_xy^4>b`s5mk8*E{?iv%;3#uR68tu)IV*BM@{djr4wPkb zSHHP}lp;U1Nrk%DLGoQoyX)6vxlUjJ&_ct8US|;L?wTEhz>jaB0xJ^pEhcz1sttlr&V9{b?Y zyZ^4z)`+=$&X3;osq3)9k|vz_6AM0%;Ja<3JzSWJqf7 zPAF(dEwr$>xH%EX1a-}&jf$LC-8MOgux%Ypli*;O>OMAd>`uUS?VtLB0U9CGGB(f3 z%jKwDr*$q}x`Z49{dRRy*3(tBxp%g*k^*%9{_H{Js&7Ydvr>00ZMj^7?sBneE96Ya z?>AiX$im1-saj`H42;S(pZF_*J~K3cUM+ZSZ`EvyoKI)29`u@rm4@4qkB zBcWi`TQYJFvp)J0ll7%p%RyHxPd1+y7K0@R`7JkN&Zl&fTaHFPz`pROE_U z(S<;QiGiVb?aJ-juZ_iq9m)|0?Sn`L#OtkIBC&lNtKp^(el)bp#xbW)=lj2NCu@;c z?vLtI2=7l`x3UVFP;EWje55x{VBttr`GvV=1$w^RYejv`CK2a@&pzX3-}UN_EONiW zi?Q`hLmR2bWURA(|FjE8UV|OhDvFAR^!da=#vfnm>&YxFG6~80HnLnm|3(q61e^ssTXi~U2UlW)NUggyG{g_yScgZOb3JHI zOPcz*$!va_Zkv)R{@D#+&B-{|$ih5WGmrOkVaLo0=qnIU|J7KQ481qWY|y5Sq90^R z)KOi;=b3Tq;zjOOGPm;+8BYh)E4%l=ZVXM$L_gA$52MZQRMN6MKoupGj4`^Kgt0G4bR3qqjO@lQ+7jTH^eBcgCv^$DXYo3 zKlAC=g#|&T-NMo&mOLN0?Wd-u@a5r!!cRN#TgZ30!PeyV9a8F0TYLw8U>0ilvcQxw zGYgnO6OwOo_uSR&h7JYfOD=neC3d0}^G|hUq94EC9sbk)Ja}iM{GAgiuSoAiOdSwd z1ATpj@1c7)^o8=mL){L3jGwuJl`G}h8KZC---`>hvcb@LSLWeEPZJ4+Yxfecfkh<1i zN$F0Ra?h^4rGM1}M`1(@qK>Y~sbyhy1B&#pTjZl?m(429_=JvWP~JzRq+Gs_DK&Bc zT|qBULqo&4%Xe^M`Jm9Pja$`o`Cf zP{CpAo0uTGqNsP!ARGk6d2z^V0Wdv2`};foQ_^RSQH0v~{`=uO@D;@;KLwoaaM6|e z2HS~*OSBJL8DmT8`uZXRB&Y2PMB&K+##MMrAnfS6y(m6)5%i?<3!U`1;ujSa$Y)sj z*f~==>I6&3gY#z=)vC`px7%{zP9L;`M3vQ>hUz$nm?=kI!UJ+c-eQ;&bL`~08vw8lq>HT^?&P;xCv zbnkj@TXKFAHywBpAy$jU&@IhwjCIOL(Tr(pDunbRe_$Ro(ePi?(pXDFv&FYU^dU(* zRTAh9Q!gBIq`k}kOM)2f{NF7%H&wo+apA9X|2BL6ZG#R?79sMd*zjWE`Y}8GiXtw3 z4=rQ60W-nu!u!y^>rE<|C;St!%~U(DS(_FzyjW{|abG$)()$wEJ`ER)u-ymG0if7U z30m2C+>bsDNV0>{wH3m6w`J^E@ggmh%aUj&HU%LZw<7yQEbxSBRsk;PZwvOm1J`t z1+JG76}mh{m2!{s9BQ=GT_q9pSM%b7&D7F6t3>8g~2_#XQy6ccESei2smED!! zui{A{(s$L|%kWlannI6A(9+$noJ9Ke(IdWWo&ti&ULG~J)*L*TUYL(c9R?waan?i8 z;Cth_V_E)ZEv&7ngpV+aGw#X}*m-}{DU_E@^AsytpLLF?8Wr_R-3Y%Ubk+0|(LaVh zEyJZ}HH)*k{a&m!7w?nB=e}F^2ucFcaZQ7nU_-G_S&Je!wl&-Ts%JN3@7<3=6q60^ zM-2g+$3TQ}EK3sERIm@4Oo>)%X5QD=Hr3C>QOjBd@-_hs?kl2M|Y89MFl9AzQS+1Q{{8o#CF8s`tlNm+@5Rl-dep)qg zp1JUh1e$PGPx`FvYSl%amPb~f?U?5*?^82G?*CeyWlOp>=9!n1^Xc;6nafb-;DbTI z2|@}F$c)wf`~CbM=FXNZ7-sydM`u^VvnxwVSi&{DmB(=-D((&kKL)WyT6%gvnZ@1% z>=9x5L&bd{gJR5z&eFXFdO!8tXMMk_MRXVvehK6kh)kE%qvV+(S$1}2F635KUf9{I zzifzs@V!Yacm4j#124i}z=wPqzaDsVmpwMIG$zqyO(mQ3odb)T&GyV#jP3kUWs;YK zDcgPgi$7)C^#nM`6u1vwC>s{4MJ^AhZ{+i;+Nt{id7qdtm*qmUQhpi{t2%XKi7F91 zIz%BDZoA4!ck$G4MB3I@xjY$s(lZM_4o{L*h$=CDg8W;HCRs*I;%#)ou)TFVnx-#; zHVtg#Opp3(%}pTo13pDd%ee!e8VILv<8{8y&XO`R2|csh8S{U` zs4OfR%q3&>-njcmowmKuQ#!mjGLri7D>x}!ndXX+&!YLsjCqkv>UK^a^SI9+M;!K@ z1W!H>;fjNX%-3i}4A<&ldNe)mWSIITl|yK}%d|aqpTUxM^?8tf2!{Y_3JDx0u;xnD z&+uZ7t;3}i&1g&)#Z?mlAJO@l)d_d(&ydQ&ajn^;A}NUwjb?SGQ*4y}WXjH7Jy?Cv zMe{g-NDD@wC=)Xu;~>cMPZo9f6CwxwgZK^?sC~y(Gu8%XNQ|i+tmkvez0338k#zh{@fK zBrwt?F_4`>E8-U0?+;MVRD9eTyZybxsua_GyqNxya?j<`*a(u<@OGIQ7@nLy(!-m- z`Cc8*Ci83-uzvi(Mulsw{7t_EK}oEibt-L|B&+&0+~X7Sd;)hnK2%(@?~5)n z&o>L_JpN>7Ue8YOpR2>L$$Y%fpv2!Bh2{7lj0-dbK>sAIR34lwWOmg_-(`G_hf5;U z?q#Rdju+5^$DDS+!_*WNi~7Xv>?a}Hdj(y);$QLj(I+-o+YeCIlgB(ryCGCb{#bJQ z)#x{nV1W(A*yV<=W?(kPmTdpw+2kPHtQ#QYF)dKt`+*PxBl&dYJFjYLqHaeV-#}TU zP01UGxiD$@{&+pk65pUh_yS!u+RJXCD)8k?#2dkGfJp??+}pR&$b#>S@qxG5tj=fD zRyHht(sHuH*WOX&mw%>&muj;|>TZ(kO~S4Ou!Fse`;Lb{lnO3l*UL zktoMpL$f2#zua(BM!0{uS4{VcXtKeCZkHN(7eAfmhxL9i2Ll>dnRIGYoN$)MbR?D= z{oa-R(RcHml?LR}s8i5wgNXd+)^w5hA~u&G~trmk01QqUC=jP_OaX-2Ps37~;iT6R|{0?;bCmMbNGZR815} zt+t;A*7b-ftd-D);79Hrr6}t-jS~!8IjV;tH>e9?y5a5)U2SPVnMskQF;1~*%Ld0B zWz=_~fbb6@-cMbEKiA@WX87ldl?&3fDwP6;~xod_{ zoh9odx*ZGAzCvpM!(n}!o8AI(_0QY$TP_}tj>Mz3!WgU$CDU38scynWJ`RKL(X?pZ z>sW?T3me65wyPe4uaz5sKZQ)&a#wHnk*G9$pmg7 z_A=A)_YY`bHc2>R^5cYB{Yw4&kttS8WY85B;O=!EyAsQv(%tIV677#ete#JjBguHG z%y0oPII-UEmG*Vv2XA!iRmU4E2sa^kW|3hTB(rHSj{Ft=UE7GHldb$p!s-B+B+LeQ zedpc0Y;D_ZWlLKhUL$iYG9-Z!z!=hUGNqlc9hi4e#71fAa46$ zLWg$01=EgRE_;ck&)HY_oB7f%De{S)+@sXn+DfhDQezgHPT@h=4^~?s-N)fg=ld~= z&XdTQU8PEqPU*&v*QMb6o1kp&lao>7wQeWG+F+pS!@%wp(V}h$(35qXIbL&#HAuVr zeY6eZYay*qTA2rghod{f;&Y6}u*UgYmfS?Fgb-)G9)*8B7P$Q-9KNXrdB}~j4h>B` zKqYx={k@G{^U*Lb{^^2XjVe47Mne`dOMW?bBwpO?C0lhaL`2rL8foJ+t5$1oxCenn zl5>Hgn+vxHmJ(yh(o_00)~r8!^EH4dXr{Vtj*;KNQ2`6_WAQbXNAxFmYm%kzHZs|G zEyIU>2-%c=QezvIB`_SGIH;hk{LNW@h`#ZUpuqjdf+z_zyLb^QTD}+*iX%COS_=Ly zNG<>cx(%?tjZlTg-%F9tH5M+3URuMHcYeq6A+5qjx$Ci7aMM19touE)Aj92VTp&d` zcK6_IXop1KxU;QJz6InK_C@D!y=#ba%&wcM8 zheZXU9lVSXxe}*rZ`{~*`Bar@t7Qv!Kx#VwXT(Y^BN$Yk2Wk0kYrtdbesy1mcScyr z9^C-P6<v~GCk?&*0%NvWoyV!LMXI-kYPM~Cc$1OIDGY@SOxKdkgZMfWeAsI zg$^mF?k~FtK`mVE&$wsB9S>b&0+f2p^Hb|i69+%CzqIyYOICe00-r>ws1JmiO8oe< z6*)fr)(t}Tzy>_Gw!Zf$KtLmD5wbBgsm*{S*6st#(i^WQG?T#@C1 zP&0hg>X<^0KWobtRlp$xF62)_DUc`h^+ue?iG5%1-eB@$>^#X!!8{o`_1&U{QIU%! zFfyasV@LsFhU?c4rr3tlw$8SMLmh!R@_XICzZfDh|9~?Yv|GpJ4Z3$=aFA*`%zXC% zt0}X;6JQKb)ghesY2{2kH_yHv#V^bHrHPAWo>2iNApsg82Q(};r3eV~dvJYLlm5wn z@9}L>$q4aP*D4s0-@L*d7}(F&8Yq{G{;=I=3eVVL&3&kRAHl}fQ|th^lZzy4j>mbt z^A!oq&CNwV4?>QB8tVvU8sfcJKJoAGkGedSoPqeMFxxKl+8avOJ3P=6CL9IS)Zd5v z$-tgCae)^Km@a|+Sp5a=7kN(gEWjW|% za!&6`$x9=~i#jFew>1}+cH6~xOUfqP3rB*NvN9%Q)TZw*#c-I=axowiW$0TN2G-D$}t?2BP@B55Z+RceO%I#+?%QIZpY$@pnj^I# zYZcS2#~g%*SGj}|s>I`T%(q0s)DfG=w{lWr&N9pjY zUU9+UPfONi7s_{)lq#iktDmssf-nb3S;G#0x5od~G?;sAEDmD=gw7U2ke-g!2joOS zo3pj?an9EN+{5n2I&GBm?W#g|(cOoa4+jVFD32NAC<+t?$R%tPcT0-6dE-eJa_&nH zydB`X;Pwi6@gF{fMkoF5c;|P~nErw4Nd$(0X3Qm4c-1@uTyxB!T#lhHb!w7=&LSM z)N^R>Z5M*b(yGyH24qNLC%Jh#Gj#) zd!LCRbXQ#5N}3ZaF>P^3>aa=CO{}EI4uGKVN4+TNPxXx%*Iy7ZHJBTUB%r^i_e#Ih zYA%#;lR=*3hSy473MJ|xBiH7QShGv>%7@X>C?zbQr4QM11&Y7pdU#k50s~4;Nc-`( z0J7#t+HU>x^`_I-!U`o2pqEJlE`4%IPV(n zo1)#$YT~xXdw(jc_dE_nNkNbe6%~6NqP~GPr=}oxwVZ$0AvNiE`i12MJZ$^Cyi*+v zQDKgm2w;?C$m2+X2YR_lcjk7_9bUd89i8QHb!{6k9<)Psx8QMtL-<9%w``ZWC3kDX zIxzD3R?H@X_Epcd{iH06VJe23Ze~B-3~kJL$F5IJaowiS_7)eSCt5&$^_Qa8v?|f> zIa;|+`xv^iDj6J@rkQQcnp8cG_?E`WC3JAMarxIH1QfQc0ZN<8RuhJ?2)j$xH~O>s z3tw(pb80`3Ggy*uIw5}<5oiJb_*Fr2svwqjOwZCByJWb@RdMT&jE5&G4wJRhP)O~I zVi6?l{53znFPd}0yy&cgq-6bIePPGd`io(Q*-wNCeXYOLS&1Hw^N$}GJ`Uh zj>Y-X!*X&^`4ZGn9|9%SSNbrX=5c_Z6a0I#I;<$ZVFGEt-@%P2K|ouUB@+s&8R2hu zj|cp(Mu4FMll$shSvWAKt$Z@6XzemNalWc&Aq_*sG_2V zcl17v*PJ!M!w$-*M1+x@e?SpOFeZ|9Sw%S)8eIz?v%n;&61!T~|hTk^}~QsnCy88L{9~XC5(!K z8g`292s<)z9zFZ657TQ+U2|?nAMGPn0Qpo7(J91oE634y*W&%;NtPJ$OAy=cE*e~9 zc*A2$=LeexnFOszrCFu^ua6Buuo4|rQYsp>%TV!6*B3#q6o0Gv)(hC;!?rclzv4Wi zz?b+b!}*sQ*fSegqh52{*;#c-?hfT2f%d!T0K+E=*S2cFi8xeb&A4u#7&$qVdDkQ! zVCN+I;}A^sDt7o{sL0oLkij2l1Gs^Y|NV!wQx9HYi~W;P%`am!8hINQ1HTY?7-gam zt*&uHRUwm-a0DTqYOO4!rtz2M<jE#*F5jpE1RJ`FU}S=76u`;BFg+8u10&n}@@W#JiS4to$iE?h*DA8;?uM zT=UmJ%n$~tabT*<-vMuOFowt8UYbzL+TFH;2X8-0Ezso*Mqe-23kviI_sTlNr=bw> z5$M+z0N4L7nNO&v+F55&oc#?9H~4}8CZp9-CHz&1eE(lf=N(UV|G)8LA38WBD{;(J zZXF|;*(1q~N=Rj8B_T<&H`yZ;GNZeJ5Q=P(WHrp}P$?}@)bH|rJbwN0{p)+*xsUUC zf8MXxbzRRZu`-|(7s2>L*SOx(|2pm}dO=i)skT(>I093?L$2M5wivhL9=X@8iShZ&=D|O6Js8kdc}CwuWk9O@`#(Y29a5rpH=Z{HHvK1UmZAN}y|`USS;uWca=F=yD3a8+z1fmUl=)_$S82;M)9AvZLF3RQKFE72t{q%c*vS_V*f-Hq^>rv#mLvjFeogM*@ zQA39qHFW~~V!S;My>uqN|Dl}1Z+N_N+rNRAFEjh-?-fXVdXPrnBh)$Q%o%aP#8%n+ zckeb$x}Q6T_p~%aQw?OehK9quVS0sM{2E}05uH+=j7l;4iJDqI_M;L~bsj3#C%_|4 z+fVe6Dn~DJ&$sW}ZXZRb0a`gIn}Q629{bW?)>w-LCe>U7>7I`RtcZnwi@K%AeLi%AqUsG zKla$J(zoxSeQP6|1+I(}+AMh%fIE&#G|}R3yt6Sgnm2Cyr%jgTXVjRKU}EYOdKSW5 z3yaW46`SM5X3RWW9@+m<43&LL6GL{8LZa_`zf=am@nf_L*J`D3=^)MJs9f^9a)sF# z3{b!@De+?g9ufvJ(Bww?VK00cR%)MhF-hOY+Z&EMgkoU87ycf}VK>Mb-i&)9qzK=) zNH7U0>!V0+O7NX<;Ul;TXKMoXKMLp3)?xp*PP-_24DtIY`*7!NiGlk1OvXHS7wacF zkG%i!Xmsh}up7u053TNb+>b6-R#E9Ml;mQm%B_D5M=UI|{CY2WS{X99y!eK$+~~R1 zp^xFh+5mnd(pm3m_~Z5ltzj>(7x(zdlY|LvyLd>%k{n-2E2D7RebqFX*N~R9 zFE8=_wYj4+FliHiDshnOH^kf2E{}A%?Tg-zbVT|QQ-Kcbs|v2d*xR=#t34*gy_Sc# zJoa`r_Umt24u#zX#(w0E)OS(mzC zo$V8re8M?9eJzu!z!N3u@tFG!YwcJE|W_X}T^mM{~yZ5^{+k+jHI6 zE+m-Blj{z0=k|qdwc&daD!hDoVKZ|8KTPI$bQ#Q~g>BH={|u^c3|60gW1wxHx%_*> zqSbd3WG%M7zOdlzZ`bQh;W!e>xjWwY>2hK+X7`|CHmT6@vV z(WS_n+`1gmDc_jxH>aFGMZoUG)f@pdy+Q%um{xq=<+!9qq0D9D?4_68If2{C0*Bt7q$Z{@I<`ZaT{`Sq}~Ja+FtiMO$& zD#1ORKR$Bh+U@v~UP+n{;i0MFjCU2%Sl#T{qU=%SaVq6xwbTQOXvhIa*bd#@@0-Ak zblP%L4Sk*!Txaphxpj@L@rucxpz|`EM3eNOAFqyM-wNE4+~Jv)EP~NDzlNl<20QSn z#pH&kYVx&M-!i|$kx((QS1>Wd>TbYP^ z2EV<)r*D0)loT-`F?@lk&3<6ltO><2!EZJH%I8 zxi02L#-xNg_5z%(eb??hR=Hyv;(cP539*FbQskFq(Z2 zURwFA|NRv)7p5>g|E>8v#Xw{}f}T^7;g?S0Q@S;yNWD61iDOuop#igYvq7bCg8d9S zzK0zRi?`~3B5pbK%!NXx-sr(o06R^X1RR$BRrrk;ae$m!Nb~u8g#|Pg=cn^}0}(UG&aAi+SmmB+7yThB#K( zk5CXr zb{Aoq>M3W0Qu|Js&O;@#obQ94Xmv%VH&!?Z8Us(Bjvhqx*@_? z_?0tgYOI4aM?>`U^E2TQmeKBb#*X?R2zf|z)K*o&39mq<_TfE#HK%tTAPA5MIbLD+ zMq_?1aGvL{p^~r}RODV@$zHs`0^8}pJVxykAY2IUL!!jRgzFllTnkr zuF6ZoSWtD_+B{AXw>hUiLQ`JuAbW~J&!es7M+MX&9ymDA$47TLh%ASk9I-h<{3mmq zk%n^(4tp~7F}}32uz+ZbWrB@OaNt=lFHUai&EC}32MrAyz_!9e zov$`cr=8Wd_qyE!(=|_AGCpl!{g~32HI!vgccb?9VpJwMCUe%osnW7u0FLrxGM&W0 z!^Zbbv-e(km;Yxr#5E8=;igjE>FN8wAG`aYP+!l5xzF%Ve%oftmWQH0yr5Y|7;$hR z1)>R`u|?loq>7<(R8%xy(0;5x#X%oZv*U{liw&s^ffsK>Yd&iR_$w+azrkjOvqufQ z7yP743kwwgn{+br^72R(n%xG)0|xQ`4&SHVcL;QJyIsESZIKr6sTO2QpT{w$ZBsep zWUic175o<%E=ewU1T&e1J3TBthD<}D5BTl*oKaqmm*&{W5PQ*>*1Kc*)oywy$aQ-N zbr!MRUr%6`V~I!#hY8V50YbQjA~G0|Ah*Xe8e5wj##c)W3uSnj)G^{dJ#c{haT*jW zEZWERa&j9 z;C#8(`EP4FA{cBtF0}_k4H1mdLBTHm!1>Ziym{lsmGxE6C6yB%Yae_I^O|8&M=KL1 zNC0fVfA^N;g3bqJu>M1lIWd%w&v;btozQdARyy`;LazS)*#WN!AEMbXz?N>MD&RjI zHVTA?E?oEq_rUS{)|zawCNch$#Ra%9+!PK4u^P~*8OHs9lknBO4M7`Yk*`m&34DDL z%U@o}Y?GJ!Q%=U~i89|HWN0{z4N@CwopDZ517|)09fy;0s#=s8qgfEjr{V-*35@2< zD&PqHQl|KwH`*Wu$l10`+DOgAS`+jEqT`e*nCcNjYFhMu?OKf~4kj%=r`J&G?pgr@ zG1k4lwUqpQ3%4$rK0%_l;_>6(JdLaE0KAB432Y;ey(7Y&!O?jI@p=fRiQt@`Sk2L!0hDU2ElUi}(f(I+St zSto1P1AQT%;U}RMHHVicZEqCaGVk%EKXXu4^fK}`-zUAxu#T`kHe}Cl)M+5NziSK{ zS32E*Xj7bKMMHhPKl+Uy9F$Lm*%QnAz&)v5T8^xQap@ZA$AAY54-?zh90<$yQjxz$EdDx9+q!rx8)(=8E$J7NY{R2)zs>OL(if4t0J%E+7lT%LbwTk z$b}@%{f(9BwEMWPxDT!O`~_a7`MGXXr)tZ;EFT${0PwdYpA31apRl~X1YgF{6e0iKZB9+8tL zQ|!(W+nLm@qOPO_7?_23VqUT&^<^I^H>CsLq=z-vCb3Xe|(L0Zw&>t9DT4o?& zbkEVg+=g!#uJVlRp_~SWU3rUzjj$49j%7ii8qthAPTS07!Pd8vV5IqJGPICHdGcv- zF+T-qX!tMNcb4EHsirBrJ=hxhlJ`I>yOH88t< z^do`L!KIsglUBKI*bT}GmPI3O4*@5LYNa{TL1wy(g;2+b-$8$R|I&nY_-Qn*1qq3| zeAO`T(8OLMe32L$92UWTs<59#B4I|Q78`1va3w8so|CGrA6t%SJI7M8(w);-g+1)&g61gLk-=pyqu zwCi`?dlwcL@eExN{%c>);E9KqMHweRXtWdn>%d~W_RV{g{c zLK?lJZviFTh)~X|;Hv)GB1M_`-Z8y2ma&RbZ5Vh^BIK1XzD>7({u}US<*8}g9}a(2 z)%?9BG|%idl`c|C7}1SvDEWOMgcz8ju|(!-@aLZMvUEsP?K^OsPG+>e$D(0!9uo|r z9K&w)MvB+hD4L!N&DccaF+rHj_$Y@-H5_vWlZM4wMmKxsVGep|TynzFGXJIM@;kM& zcmO|rk|5Kaig-`U({pNnr|wHXxJB@+-fh%aVfm@!Mj(B{Mtw#;iZrMa5x$bpk#s3% z3|gPsT~+)a!{@+vvXFXMSxNT?g&%lJAOt-4d2CFeRbnr5$qEjAtOPqtN}mErAkV&> z0O9&Z4`WkvN$11^%=|w{B5{6kM-L4@yBBKT{OIMYS92hTG8WQ9Bdt;_HO-%gbT}@& zS5@nX!mDrlEUUl3oC9B5hnv`F;7HVm14^DmM8o`Kyqim!; z&`-T^8Wn{X zT5F*lmWl#;;s?)ZvolEA9r!v^oj9#8FS)m3ZCk+cemHJ5AEysEommOq}PLUZmSDUJkAVBS{;SXO5qX@0`1^MZ*R1A(UlU!@ZaVkGnhE|0LL2fo^E!t4OuGK zJjR1Tlk6P6W)4#_a8YOr z^XtRj#&v-DSkI!NM34S(nAR7GL(I|HPgwTs7TTJl;wBM6%`)0Fo}k?c5HlPDs5l3o zHmXAo4xt+N@n4880n2&niT|#B+FZUJq6vFxY3@^_T7}!64ZMs!%2P^CBT>&`%YRXA zE;(p`S%IH5n+{hv-xn^ab-B$7u(<4m3~KTZ>JFjkAU$MUf@45C^RQ_9ojemsgqC1~ zhmd9#q;-rz6Wj*uqUB@6X5|XBY4hxX6w`$A8a_&;L~)(0okR%R}c>UnIAKTim(0CFP18 zDu+|6avYU=bVr|8$u+qAEio<`?0zAC^48$3!I8sT?Za5PX@o5eoN4kIVssxr;VD~K zSN+zl`|kI-A@3JXmveuvct`DOF8_%obXd=#HxAyoQ0EhM=?K?79@n#>zu0Q>6PoJ( zx;e$X(w^6$^kIl*?2SM|MM237%e$;2gB~xjtKJP%YyDnD5xpM+_W^zc6$UOptInG0 zSvRZuyUoPw9*-`q!0y%^#^;MA=mcY$L5>3D5rxPf?|9}6NC;of3D5;!NjHq>bbWV! zFbJ3*w}f~-I^(KWu7{0`hL(I3l#eqn>`c9C%PJ$zaTn4Y^v@(4S(5+^8*o;SZAlCf zPWh?tDSD5WBJ03NNOXSOr|0EV5sBKmp&>*^zO4CUV$c?tryt?3+-!Bf?BnnwnJc(B zz3{%(Jq%bFIbHXTWwxcGrX0(m(qN>SlDI>qLw$pu9k5$l_ya3wwN?6qdoJ~;vVNnc z7XPxL#_FGCLH-?XvGR$~=aF_Omi_b!gZt(;7*}+Z?>--NFrGOu5^n|U~TPTGy%g;VghmyJU$L(>BgR8qMc*WPXaPbgQ`Gc_%KArIT} zUt@ZOn$Mu#qEzOv7-!yB7$g^e{@%3lPqEdF7klp-A(Jg|A$gPhBu4T8Ng1mj->>)w zCDgcCgQ)symAc6V+$;=t6>y|WT7nx`*q`TD$fv5RiYP|vM{(Z!2uT~4h^kDERG(F( z`}=$1Z_AaiHi)nmQkcDXgMboa;hOfUL)291jg8Gmwdqi-ZzG6rFfZDjHRm%RT zm0)XEIprW(iU-stN75X&RRkf#oY>jZc6M)n@ADX&6a;Zz^SxJi7WCqy2E_yRP~Zj#LZ02C*p? z64^pU@^0z%8=Q`E_PTQA#^{dw$&((oomaZ&zMg-wrsvu??p`zQKHq|~!22@#jH!kV zr#^et74$pTSv$r+p+?HO_cvgQ>>JiK&8~s5qZ(e&?j6rKY%?tSqf}*D#%+BbB#krO$@C zzM!xE!0O&+yS>;6#=9j6*Rqx4u4t8XIP3e}glTH;x=%bDA@I0=_ftF7hSjnn`R<7^ zq?l<>a$MTq&gv{Y0<#!ZK_RkC`_3z?d#`bx!SPey{*j)_)iAu& zdq`G9A0zV8%Z-KQC|pay+kZqWOmzH`VyjkWek?z(aCk2wCV2>z(F_u&N|ZV zsV1XHY*)Ijo`akbw;InOzMZqv(*>Sor zKW%j?edYRmmC|l1opD7q7rO+zif9Es58~SPCw~o#fv;9lkW{$6&El7Zjtw7{9||{i zkS>&xm1*-Tg8xSi#H6q_p?Va>HxBsgRB~Pnmi&tj-eWs6cW2$IWyJJ7@p)o+IJsbl zQsw35w(5eonKFjEc&LyLXFgP~wY=SyH~K|mmm3i5HsqJQ&L38TB~ousd~OXvev0!1 zot0$uNt+MyDc(KdseO)yw+6A6g+Z3quj%hg*B>>BIGwfp?E-YgcU*0+^-TI`qotu^ zBn&&UOAvJk%HEbxg4kbxEByAC%B$wVK{E8pAg?)?bzhIre{Vj>Ws!I(2agOe!MjTq z1%>yc2$!mu$(PGg+*>T={13_V7oTk!M*>~4NJ9j6*LUx3fv;*Ig_n!FJDaB&Douri z-Yj}!NrQ`wYc-0@4ZWainXXSo2`LLCKpyUKys35txHfEF!8D;X31ne^S}M(Typw;| zkv3aj&KsG5M`S-X`+onu;adIvi*KetPWK$~6%zw`6RHADM!rivmzzC9mq0ZIAqzfF z(YZ*b9VEu4X&t_M_wT>x=@DiI#3uXSE_3VBw zY`y^QvRta<@-o?-?z}gqy-1gd*6GNt!9^2E9qV}Gk}yXhKEA2QcUQXt|CLCst>xQ) z&`qg|FmOqgxzrvva^v#}e#TcG=&Vj8pBFtec@3q-r8yq^hL%>KU?IT+a#hSJ@$m2- z`bZr|qIh|;jX|;5%S`Xl6K(w5oc0s#2UJZdF%yoQTL&cXt0Km8iOj@hmA zN%IB!<8f(LDnS*X>$sKKeN@A{$^z%R%_AGbGT>6fF2X@p_(c9kbUGuU2QV6~Er?dd zX}%*_@V>D{odnHpMtCYjY9dMBK6r>pTFM7lDTeQt5S*&Y%L^p1WT&IP5nmfL( z4zm5*R)8C_9iVQ_OTzq&RY0+s5`e~D_9(!@6I3mz9!Oe(#}C~q45YIf_;`?b*C;;p zP=K;+XivJr?QF0%U_vFzs;lpnVI;P5s3yHVS8WwS&tYLYHF&jo1Y<`>EhVk!I+(!O zHGMZ(A_~m!0K(ZJbMlclv!U3{-a?j(iUR15VhnqjtRW@bMjdseG;q*Tt-I+WwWHRjA+*&grui$ zbEWtw<;bZi#rjFeMHd(poGE(~mf_C+ckzc`Q+j8u*mY)4m9EnsY8oc0I)NM-*uObG z?A|PfzP>5`urRo{HrpTrin}+!DrUE#1p+Z$*dLdW@M&qOS)uQtM>?^!Nj53eu@V+& zs6~kJCu{%s9q@mLCgshk}`)9!f7Jd2oSbH zl%g+cO+K|H*-Fv$g^xF}d)S%Q4GwVjLJ{C#9Dfq9y(|J{V7VIdI}ByWNjRW;JdC6V z+kETH@BEmYY;vt`xlS`!$XM7qd7RGW{f}RpE=?#uC>4Vo@GRttIRw(iU$4qKcW372 zGKS@5X4bb9HxPp>}u)hs`CsJY6(D!Gn#wDq4GC9wohBIN2#~H{ZXJPY8MW+_AEA4b%;g#|QCu zO0Z~S9*Ea#IMr)ua3n@|8EC=F&&+23oA|ubYhP-YKgKy#XIf^LKPowFuG?+48CyF< zwI!(s{Nj=(+Awcb2|3wWM0wkFG^s#AV+JebOc_@y?>WbNdxl!9a5=sk1W;rBlf=KB2e_mEvVqrqZ^|?gpKGuXb!}( z61YgNKcJx)@>T3r2sxA?t!Y+pLCd*A$x!zq`i)a>u7`X40W$!51rftxrssNW`!2y{ zP6;4hB%nnZH zR!IEX_yc_YHY49sH^sd^RHHRY7UrRehAZ$%iE}a+aA9TkzKH$t{a zPKq*4`q@3#RJJvWFWmcg(^1B-%?V@E8s>3!xdW!4;zJo4MQ!kCW9oe=)-+Wh*|K3~ zOHD_tcXn(J;?OP9wiX@TvSeBwjD4Iz(^7akzKl zjhlj{K+_Gt6J+4E774aPWw|Lf==NNqZ~BnxuH!qu|2=O7&bCQ0-93!V^+N(;kQVx# zzvBr8R>8qJCGBx#W!NE_@)_{`aBr5p>ecL6nQVvQ2F<~Piw-QiJk3mGk`2VTuU3zq zbF_p@;_>*OkHeJ!l-gA&Dk&Z8J5u(hl`S}sCgspQ#GTx~$HO9NXJ>~cOE&2S+i}>3 zH+T7m^1_*_3e)OfxUjp6D56mBX+Ms7p0Tb@3#5F>F*<>mw*f{=-b1?}J{1Z(t_**? z@4nKaOnD8L$i%31NgH#W2lBr1iqr)EEy~ADga`{88v$-BX;aptUv1?Zsy+i_9L)?g z>#`1(GAeRT=L`Ea>fsy^j|OiWkvxH`O<%;qZa}3%7`-P(c$l-EY=W5AVybsb*`UU=()xE&4 zvt585-A%TZo`2pNwkN#_;E^514=PZ!0euoJOr51x3c)&V9EY2A)pLcuTa`a_lnhDv)QPkE24F_No5Z*^`z-hnR-?<#PD`X|_K z;_8*Fc12?Ndi273Z{@1(paHp7Pao)at>*m)|J{ygLTLF__?nI@kKptuc-$TF2;>bI z=U`{S_RvlBwPY2Jek@(!^pYKB+RC&;=N?uZKuc#Ch#{k#`J(F-_W5`X zY6KblrJ;Rl&WLNY75rw+k;Dy39j4%Gl#)S&9Z~%mhLr>61)#=@_8twdPxE<b`wHX1h|g@^LrB2ZC-*zMPnB zn(Lad2E*Wcqlfc7NR$?FX)1aCR7+UFgSTR@cy5omk64hTp*X8-_CLp#@T-dn%`tWg z49wLH3&4R^gh{3mG>{&xMrHg_Q(N+?i!O+4o1^&>b@=dM*lT19%VTz>Kl4^*wdW_& z<9+mWMw6v;j#e1{(uDhbWYjSSpo}p(dzp3RcR*Nk@b>F`C&t}*6oU1vxO5#ku*xqt zUq5|!r*|+^v1C_`0XM$|RaJn?;gQ2B9X^HYmE^0e;kRxIT|a2^ZDo?*_i=b9~7@_kFD>tp7=D!RCGctbuUo?h@7_{o>%%RV-c4Rag*C zxbWfV#9w7KiB18#iq7}Ao)$SwKAz#$`amn4 zt-q`KR=F`E;)Om6Z$t&$gMx(olD|%(cMH>tk@(Y!>e|{)3xkKOZ4jD}1Vd)Vu@bpt zyuknnb3Jzsh)7ObV<(;be>AU8?3UKy*w~?a3SDUtTEUb)75@w*lmPU|&Rz#Mf}2Q> z$NC903;g3FODoRucs<0Vq&zA=&oF|&FDU_@&B=dkOC>sL@Fhdc_uEONj?C}q&gQ*x ztq{WuEP9*fwLSY~RY44&#Wj>^de4dRarkLA8>WK?QbrVUKBis8U@o9ckNix?lPF+^R!pDWYs~06}a(vA=&4m;H5wsaS+7V z+QBJ8!3EAAE#}v%pi^!(+5%p5vp48#*G_rvarcZ+l6^JU4nUD-(`gn3hq$Sj`QVuX zyHJdC%|BOt%T?(!hb6hDV?<|by%#y(x^YHQtTQLBibW(sBJ_BC`_+33sdrv=m}?a| zchP#ZiJT~Wua(vI3K=`i;mwGrFuD6_oic1dM0(6WO0S@KayU-D05(0fi(ITrhfy$B z_3{HGuX`M2L}#Lt^nPRaoZ8hEqf4e_ue7%_Y?}kjWE5$GZARDT07v+#Qn*EN66OgU zykk4rWE)PMzGse-YU(sj0RH{nHHg+tc>v`Kk-+1xY#md)LNz8rm=&SpSJ_iE2COd? zc;I7(ce$K=&*1^OnVl`4WduHM(68?<|ErzB&Uv`P4uoB-74aC$Y<^O2k6I*|O>qmAbNT+U8|0azykX5Yj*cDfHNXbr?c#jYL;YPnl`d8@$hjuwQ&>f4*OX1i1?}Yn z*S9~{Cf53{Rz>*&VTwt%BbTw6C-G_5h*og-?q+A2Y&a85G@!_?Y_C%q>+Y%1S}isD z=<~RHuAmZ-Yzy+`m`!a3F=NCMW7;sQGxS7A_Rmh^%eQs`%Xv?Fxk}oat)pRSttZm||r$Nv?L+>e%)pTu(J5gnvbdx@Q%xGL<3sTMlTjjsur1!Su^D44zu4xZ zTi927p=A_QPu^6l}&hC8?gsp_Ts9na{b7q~0tc>DXSa>fK}R9W4_-fBI=uR-t# zaWPhE30w{Gf9^H#6ylp&I0M4%o5O|u7=82#Tg$I3x8e>YzYO;p_BG`f0kiEJp7TOI zBTT|b7lsgC0Vug?)!vEz@Lif>cTT{rF0pB@g6W|Azi@hu_T{a$DPqm61LY=?ZD(|f ze!xYJ1vY+$Q}G})$B#puk&1_a zw}GqfoWh9;3!S7`0rqQKb;~4U0q$l108I57%nwkLF-rt~@7Je4^~FMx8D?SPhH+&5 z;3BF-VAdC4ceizNdOH7&hOK`zEcD26Lq2}2p4`0enRkyFG54biigZ(}BFNjj>d~WT z!bY5QIYn5!{ISKh_00#EqJ{~Qu!l_Z2nReppt+>Gm4n2H?8c+rxXa>ZY!d!SZ$RT< zIrl5}7!kx_c_lbwnmoX^L6Nr4V;uTBW%${x)!WfGLA7_^Eak%4g)ZJ zyy}d!jU}1>5!6+4jm9_T?9$KQrap3oYjAm-;auIfVxAym<0(Z@JZ-zVwJxch_?$sL zKC2n0oU9>Ry5BiSOACj=^MjAak?zK}4t}zH6P#_Pck;sXpKz%IuLpFdha%Yp-f_iI z;}W`V*4=YI{B%v}Z5&dB_Y&%_uAN?<<>J;bn`}f%VS!-;A@U|ueMg@lMaIZ?H!VmB zM-rcY1GzWm8t*?sEu!44p;6y#ZPxIhU2YLbEl-3{Q7HZA3~vg)n5kXgarehM4GZvx z`~zur5t;Pg-<0lQ&?GX3S)YJ-vFMF=DmLw&)6X1a5paR-#Ktszkl^>4ub|RRVT%XU z*~Z`FpD~f@EMW7VP4lr7dKRKtaq;oCV|vE8=03GN^p5;+q4@;O#j#Fwk_kA1Fm*Y< z-Ol#G(~%J?<(v-MrigDV>Q(3Qq2m)l*(s&&p z{ovo&bz0r~Gl6=GDio*x{DaO;yhl|P&6=FwnuyTd6S+vI^R#^(_N*}&pZJ2>v0v*> zajh982|Hyg%=1MP$M|MN9!x_u`3~ReUZ8Fb=*j=eWdB23K$K#R1@;yp$uH6d{+Y}1 zpHgE};D@#O9~C@^pr9`d)for0wTrgs?D4Simm0>Kh6kZM03u)z~2kNJ#FrQnN zKc1wGZX9r!#@$mbG;UYb*Sldqdqitqd^`qIG2)uZe3zAW3z>zMvl22cDetJ=aw`Cr z!TuArOkurHA@O=3<+-t zv^s2W)&u|I<(|3HqsPFM|0uL*>t>OAo>B4ZE%dZFVw!E>^t@ptL6T1K4f}AD?X+~p z)}vdRb=1Na&<%z!GiC)|K2p12;ynJvxSFf77{p9;53vw3p*-x0%*3l9JRR6j{9BF= zYRJJKhi;ur{|K%A&p(U(Cm}5$X37##f2yn}?4w&efxu{dP)GBqRE+I%Z} z&BdDEDdr4jM6yKj+JY%A?}`GatgNOBaOJ>{45b!WKRhq{cZwVreED?a_fI8rzMC4e z#2uetFV!*iN9VQt4HWnRe7h;Z8)ab{uC@Y*F@+iI!KCAtCSrBx%^wWA!Osjz>LbSZ zTx@htuB)>cadBkF*)&~H&S~P5cS+hM#Z#oGoWoYzIrBwBM`L*)=gVmZ&B+}IHO`tI zZN|fWUcrIeR5$N4lEh0s2m+6E10ly4i=rei#jZ$E`l*!|M=hqqKI)u0_baiP>~Xx^ zN^t0Bwq5b4v&Op)?gz)p=BCV^ci38>{KM!Yg0jfohQqZH+QHl1GXC#Z40!y zLf%CK#XI{1-M>2<`MI}>u*(V8?2C}QMQd=_m-jBeH)H0w^Ih`CQ=}-qf+gr)5>WSDR;j<9;+h+W*GE6uMlPOk1GAG>lB_Oe6g_QI$Nb%rM?j;q`mdr z&Iqw>GSgNPes~V3XSm6$YU~lY0+M$(%sUjW?=>eVuq(QPs}}OFq}pj8Wr4Lw+3@-9 z#x!~L@^I4jye*KD$$2XtS~7_#e~-Jm-RB($JGU?` zMAlW{EQ@LY=ya!Ytbi6%?Z28}rZy=gn^j!jRcUgd;_*;Tbfjg_%Wdm4VvXglrkmx3 z^8XsdUk->abq-j_$dW^r2jyCLq95)yG)uEp<#tc3`5^dqmr&j4(lg$Zhm=|>Z@>@6b%v^jvGH>TdBYaQmVN0Q{V04<3z%U83uHPlC@o z>=j9#3WoFjhI%AFlls$2k<}JEwcA;*|+;;Am9JywkRmGBNg|^m18PEUcD7zC| zMVh$W9crAD28SUdYwk&(eOkUm@hZdU?Urf-Y->J)4_5k%`YrqV^F{@ix^kaw^a(c3Jgq0WqDd!T+<2FuZBGwY4Q+(N-`naW z!jv69Ni=hHN!6X-(OB~nWu@5L*F%xR^63PruZbtRAex-hOZ%>yRr1_UXj3LH^d}#C z1bFZs`eY_3)kiS4ZQH$mid*1jwEB$(L;;dbT22}Y357JQ->Vy~r=?N@QMxFUy40p% tqrQBiR!ur`s*IwN Date: Fri, 9 Dec 2022 15:47:44 +0100 Subject: [PATCH 19/50] Abort initialization if unknown sensor dimensions are reported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zoltán Böszörményi --- libfprint/drivers/upektc_img.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libfprint/drivers/upektc_img.c b/libfprint/drivers/upektc_img.c index 8898c5c2..f2c6edb8 100644 --- a/libfprint/drivers/upektc_img.c +++ b/libfprint/drivers/upektc_img.c @@ -602,10 +602,15 @@ init_read_data_cb (FpiUsbTransfer *transfer, FpDevice *device, default: fp_dbg ("Sensor type : Unknown"); - break; + + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Unknown sensor type (reported size %dx%d)", + width, height)); + + return; } - BUG_ON (img_class->img_width == -1 || img_class->img_height == -1); self->expected_image_size = img_class->img_width * img_class->img_height; self->image_bits = g_malloc0 (self->expected_image_size * 2); } From 86566e8d0b7b36ebaa5588af75b46e2c93a12918 Mon Sep 17 00:00:00 2001 From: Enrik Berkhan Date: Thu, 19 Jan 2023 20:28:33 +0100 Subject: [PATCH 20/50] goodixmoc: Fix missing "enroll create" state After the driver internal duplicate check had been removed in 46669e9f539ee48d6ef1ac8ab0732d5a0d43eada, all templates were saved with an all-zeroes template ID. By going through FP_ENROLL_CREATE before starting to capture, the template ID returned from the device is taken into account again. Signed-off-by: Enrik Berkhan --- libfprint/drivers/goodixmoc/goodix.c | 2 +- tests/goodixmoc/custom.pcapng | Bin 73120 -> 64852 bytes tests/goodixmoc/device | 149 ++++++++++++++------------- 3 files changed, 81 insertions(+), 70 deletions(-) diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index 48dafe10..d8f11351 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -631,7 +631,7 @@ fp_enroll_enum_cb (FpiDeviceGoodixMoc *self, return; } - fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_CAPTURE); + fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_CREATE); } static void diff --git a/tests/goodixmoc/custom.pcapng b/tests/goodixmoc/custom.pcapng index b5e2d89c75a7253571360a9e064bb31259834e2d..91925244702c6a94ad5524bbb0ae5a3c4e42b1d8 100644 GIT binary patch literal 64852 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

L4zz?BJ>h*kk0r(^u zS2}hGk4MZ;Dg{~D?zoValRww2juL<7 zcwEQ#U+%4y!td~S;r0`*%zwaIH@@(n!2hha+UIu+PJ{S=2>cMrq}KRx^1m~~-WnuwP5C-nnD`yZnHwTD%hmD#-cd^t#nnK8cJ!bABT1ADWri=jnidMSy)) z3H!MP{hvM?HJ_xfxAiw~PweN4_MSm|B!2QqqYZ9+*`Ir|)cmQ3*_ipH;TAW($R~pT zu7>{OaoMvrd}%xh{IZSI_!~C#jVb z9FPV;1 z7>{K-s`*n7voZ5Y`$KMgIiI}I*?&B)eASIF=fkV-P~&gd_!oQ47LU?=S@?TC@JTeT zbnFlwkH{xC1X=KR+RrVRf5K5Ek%ECr@_6k2y3Kx%@p!bWn*BN_L*sGbwgM}kM4o5P z=X(kM`Br|L`-R(2xHA9Xb~nE4zj^%gsfm8xp^!5m{wILHA7xUne9{k}7bOXCrDKP1 z{@BmGu{C`@WD$087h_NNe*9$WEPM_|7O|hZ!sGKVMUPba)5raVXovdG{`r^7o-eTG zH<9t{oZl>Np4jJm3-I}@02}-$_Fv}LI@ly?KI)40G;kobp0DP5NsNbg(H@DP`HjBQ zjW5STq2~Ydp?|*U#+UQay>0w|zPHa_8@|VQ{J5Zr$VXTI;{WpoEk@Yz zBjd?Azj@_XwfEbppGb&*A@D;uCbhjM9 z9c{z+7>}pVtND}KAt8URpWw!q^GWr;{l{bTNp5^OAHIA^jlU6QZ}R^U_#u=@z4FOe z-t!u&kh&zGI81 zK9S~k>-ql6boxURv4q>NqmIn~*A;Gj;Xi?27gIsM?++b#c(dg{5B|v~P4XIENL|AD z8;n&Cu&cho_205?Rel`6?e2W?&ZC<>*Gt0wQm0J&6Id^m!zX?YvA_F2aUa zLO_%M?dzp47d>OmZzBC?u9t*;Ro^%JFW&s7Kz#nVREwWF9(_7$KB~Rg?JqIE5$%mZ zdnA75H!YUf@IA)!%n#J!K@YPr^U>L5ZhSc(rGLU5&hK-*@XLfNkH_Q{ZhSePw)|9$ zzrhL6cpL=$5Xz+Td|UMY8sL*?T$oImo>O+j|oI$AH4DpIGrqfP<^19fEkhdpew zKN64TdPDHra6-*~opT}kr%&B&<)g^+&|Gf_{Js1A&riosbK}eYdk&xfR>`N-Pb9?u z5b#4dCiTiksrdXiiN=+V9m4%rz}U?Jwy(vJHr-0THT8E#l|%{#D#`hz^F6z*d=eRd z&U~^IpC2dt6UZl51=xQxvH$Yp73Hc%%_n!GJq54b~$Ky@2DVDW9+6M*&C)6#Q$G1Z z`|v?WodgO7>d5)zIQVHePb$WX?7#AFsoAd+7KFy*I{hUVzVP1$zFt~4gd1qy=Kh~w z)^SDVKV_f|KQf-od?@gz3{%U824_J0e*u07Wm0SWnE6oP{{nmxjVm2Hg!3<8EIokV zlEm*djQn6U|B`{DV(fx}%W^)Ml>3rrJ`wyTjWF$xmrsBn{yqctUye6uY7V5_#_%vI(7)>Z!lInz}oJ@wLjNa>gcGFNCEe|^2zOU_j>NXi1@vI zv1xw-`!Cm|vi;534n2n`N85jS6YXiZMry^gIX@BYy@~cn{OrGMo9D)t{kd(4nm_e0 z8#A9&Smef+-znrYi2pCZ-;Xk>HGZV|3GhimT$oImzoGF!9HuM6+J%w^MY z!xSj}9*k3H|Ao}+_Zpf%U16M$`wP(u^`CwJrS|ZB*8C>Yf985gIivzbRns z+5nqB8SAB)UsbsxYCal)_B3!HwffVUk4B(95Xnbq0G~wTO2-c2{E?5cf^1U{ zS})}k>^|wJlR&{h9eF%|J>F)2Bp#jXjjyMw*{^dhG@jRXdo4=*Imh$byW%_vWB9LV z4>!K-zoLEAe(#g|iG=t+4*U>~Nv-kY>=4esfU!CO*73Q+Rj$cdGDQ4E zbVtpY1p`;*d{Va8YgRsqj6Y{SDcdjM-`_1CV0GU_J_)_}#UoMk$x^hZfd{D-&(3_Z z6z!4t$tPRxa^uVX+|pmopL&>$>Ccz@y7A?F^70`6@pz`64c}utoyk?>Z`kN)!nS@`6mz+ch5t74 z{g+MnR8LId{&NP}@FU~N%!dNM72f6dyPsQi&=D)2Nb@s+|0VEAG_G{)5YE4V zv3dcP@eb>eJMl60x}!=W1p}4jeDXr>5zl-g_)WpPl8Nl+0w3SEyzFP}zie9B;gzWQ zBz>^Ezs3HGXzwiABk_|@ZX065m&T*OFOGLLRQ#!j*_ioc^GG+o9FHUU@5Axi+%GZW zvG*u9zQ~7yfB8%`{)UbJ-@p%{Oe#O$1pYqYlW1J&*dd%h@=5(5{#XU=zbqg2*KdwG z2^0*}k;h}3F*f@>#$(YsYWC}#3+0m@>)y5UiN|=na)kMPPJWyFC5Hb38{GJ^|6UuZ z_I*sl#(xFyLnxD4&tr3bBK+4Q&*ZP63aLvte}l0G0k)$9yN>M{Kk|N8wRk6Rzbl_S z`t-Y=`!9lD@3E%+^wf&=W9#q8^TC{-i1uDXdnB>i zqU$l?S-1xFTzZ$3JPilvR;&H=PH@=)thE4Pzk6&!F;d_jyrzWZKH^S^q{`;Pb zACEI8Cl-&$Ck=xv{axCBd1=zvp}d)dUx=6NzaO^8xBuBGYWC}#5&5s?q@!{3$u|xC ze;@U+$u@kC=UJadYTw5!wDI3K<*4WWix@w{8=L$?xR2B&JfC2^-rAaNza9H8Gw_?4 zA$m^~|t4+-Q zOJM&c{QYmNm(CyirC8K_6heC%IFMS;S7$!bnwtITXMXd--8OuW@jRuOT0H2)6+-bm z<32aOoR6Ai`;X^WLT-FHpU!Nh#@`6DH~H6{c{J|x@mJuJXk6*oAv~Uuj~WH(?^a{I zG^*+Tb&fg-6b#gn^N~KwW`86eo$HOXHfr|koD2Ey<5HhSi9d6Gu!*mi`aGDz@9=ox z_7kqme|>2;zVM&Guf9xe|H$Aph=1!cpIZKlJkOo{4*{P<<4VU4;rt62!(Te6c#1X7 z{qDNT|C{yFp0b}>`6M#_%zPs3Z?wX+KVCjD@csP&dpis3rLwDkxhiTt$t-8<@5p@B z%qOC~ZD^0gPd@2&r5j)N=dp*?{Hcf8nB%c@1vkFPCxZWjtNh2~zKS+{X*>!1W>2W` zH*EZSUl%_fj{~1X<4VU4;qi!k(j>_4pGNDYuQwUGqfP<^19jx__-v*4_SZJl?AJLL z8jm}Ao`{=I>K;qwcX+&T`w3U(->sJoKQf-od?@_)-bZTR=QKDC;(rkMA(Tnw^^(BP zK5p{YP=(YboWH>szL{C)Ep|Qo@%#6Cx~j!Hf&0Vu+vg|QIVY_3QpA2|J~{ZQX@3Ii zrR!7K2Q}HXjYd>JThZ1_8_=GHYou0xIP*!36J~$-SubtsYs2>#k2jrE^QTT+A>_~Z zbT_`7Pr7~XKOTz?bmPnU@cvV3{EaYslmAP=51~wI%@3W=waR$mF4kxdXUZjNIW{nPhk#J@H z2M@aOW&dTjR@*-+>bS1+ACv+3@LK zu6l7!7`QHv=k15%+h3=vn*BQGLH_IW&^K26N%n8%>!rtz(C%Xl;r8pOBJ-cO+=d?+ zPi8(9_^-d0r1XA-!Ko1cDl5LR{KxS{{0aQrqb7e1_mR4U^Dkfwf9dPbBCK!j_~UyU zx~j!HVW5JXPbNJ4jb}a){Id_D^NrpFVDN92>1L9CA%)=RxAPgnkf7X~QENFb zsdE9p;r&4^cq@s&;C@#=+5XiT&-IdsuV1Q~_9w7jx&hztxQo5jTswxgqOF&bPuu!C z@_aDoC!)QU)y)3zvtF9`wHsgd=Z@-X{?uU$3B_Z(Z{7HEKACru|9H&)&W7(Xo@U>y z#@`6DH~GH{{1D2dRz7!*r!u!B7LUj$t%7W8XId|{STp|uL8^#>%5pwAbT+>Isp)F= z>zow%?~X^##?2=W{)7FOp^w_|J)URl7585XZTxFKcGh#fB*xEhzW)-!eWWho^AoI> zTDNAkj$oB^AO0R&2=6UT!_ib%eRI9!{rTQieXh;)aepBilIJ&RtIk^Un@Ine>m^~I z{;|1UjyJ!-{OiU5`{4?#mx|85s$|rBGz;x%;6Z9VU!D2r0NNw*GrzgxaT~tJcz*B` z|Lgw_tKIl=K3d54pTb5&`;W(ij%)IGyuone%lUMm;{KDtIna1q2mBDqq+a>xE8vr8 zT$oImmrb_#|(LhGdmzP(j>!fgOlwtwoAHv1#-=v;5?D)jHa`wssaCH|cG_~lY@ zo`f;{mw&{KFZ=J%6>6U+q<$hH{}q4tSId8q=ed*rZKX~A8tx%=3FlwH7=GDo=8VJX zx3>O%;}KW2cqa^0kn_p6@BM1!lgRjU=98Ptn)WA<&{?x;4On?6MK{WnOe>T0!e>|2rX2bUwPaUpS<8Rpb zzX1FY%A{64ck=%o_#_%vI(7(;N8}Uil->3`<&)bQJT=ErCxL>2I&wbA|0urwhpthx zU*}wCJU+elch7wCG~a)D8Urn+aQk&slKEf0-i99;PvQP;BJeMNO6~V=3{HmlZvcJ> zWm5ThAn-Q;pG4zI#}48AVJ3bl@R_bhTJ))?@BiPdmmb{oyJtQT{2p9y+Mhr^F@Jxr zREvEdjP4pWpBzVf8jK^g`oqj8qP^p2kHk+t`Dn8ZUmA}B|Dz3R{?x;4%zV=GX*a%@ zp9uV(8~F`hFWjGmE63x6XWaNA9}4^l7|1H0Pc}FYipRf!A3~W_&L;x@Z{U+?T$ zoIk4F7G$diQ9cQb2n}=8NuXe$j%@!ATWt1wjK?1~tJ$w}E@c0U#V#?lweN=;Hva2?A3~Yb8b40{>wr(9aiwF2aQ+31bqKI~<{W11 z-IbG-x6iQcX8O1eM{CNDUlu1y-eWWho{E<&?5904hP(JytX6`etdT~w|xGvj&S-JT3 zUshVpex36m`{xf%X{Mn}>NOtc5A;93Odo2)kBleh{3QJ!e1Gf+@I# z5{)YzJB0hMfU%ANR`de9E4S#5%7u<9i4+V}BLCU*N$ue&%{t>8so>YGh%=wm=HDNS zmro4-d!BfIXvO-Z*-`V!7PP0KDN@m%`2LZ|Ut7=~iJyFOe1r|(V>})o>VLj`ZloJu z&L`)F`H#nzquls%K5RKcjlU7@O*0>!0DcH%Qm^rJ0{A2vS2}hGk4NMaytCfmb$Wm3 z9pm3E9d!~Y7^oxX6MQ#55+C~?nR24q(o2_Q`$y!d*{^dhluy_PsV;okf9yT~zX$fh zhZerJB0B#Uj;j407WESe@xSj_YQ%p6U*><``-%M?n1Nv*V6&#P8*ZpLt;N!C8!G?d zepf!3^GRy6jyO-c{{dmYGoQ@)$h4oL%#1V~L=o&a*GOfRPp})WAM_2bM|*$hEwrcM z8mVYc&L?l7J(5^$(RCdv$K!XOTKLQ(9=|)T=1*#eg#5X#(2XzqbKR%@8)C|H%D!HqGxJ zdA~nY_vsg(^>Ke8h>`#8^P5@k1S0brtN+Y=B>J!A0<-_(&2I`As~KR!R${&M-|0_$ zA2lCsMtd4Kkc#$1J`(MXM0+HD<~P?Lw(v!~iRYuhKgsP$^zYZ`VK(M?-utc_Uyg_A zi~YxQoA+$^Qa%#=i!M=%XT!$-E#QYxCYAG%z+VJ>5{)YzJA}tG<~R6c+n5Jvz1029 z;-?&S5-1p`Bj=;;M{V{?&lAC~+)_3BbB^fe{@O#)pO5c$oWH?X zj{sZWi}h#6&KBM7sFFwl_q+1R&vQ#h)=QxWh5gR?$#X5u@1rKLUb;Dz-Y;l;xco0@ zE82QVn`ia6Xiv^3jDNq_&wA;B`EGpq`Oro2`^7qJA)$D@xX_I+`}0M7e>bMYh{xb! z3tz;G%>VVaYTpkwI2qzU6!;;ONd>;lzk0jG;t~0zXOR87F|C(I@9%e-AXUUbWjUWb zyToR{M?M+TUd?`;lOp?DK3^toK1m*1lHcL+62pIMc3Su%U(5a*G_Iu5`+J2p{@M#= zBI_lAFZ+*;H~EKfAE`??f2^18Y|VPF#pmhh@0Hw(&(qz9qj|3S@zzT@pT6zq{z5b) z&u=E}DifLCSn~mAKB_mt?7w*Pn*#jae1L7Z6YHfZld9bsH6QIldm4Ct-Rb_jJC2*&1p}w#d~{-;WxrU@$o4-m zP0fCtH% z%zPsFZDxsmJ{S1-`v(t>#(JsmUGoP-%_n2go(3MIqCJsMM0-D?JrY0pWa@kiU+nLQ zcog{Oll*_a^NodWd^sN5rdSpj?!jVm2H zg!4BT>l0uLC$ihv^f$k|&rv0j0`3p{&%U3VQKDjGy(IFr*nbiIyLGl{e*){JTB-PZ zv6z3&x`4K#t(V53Jq_1LMSF5S`3dcj_*pNFDQV%0=dT=(Kg?0{rygcw&QD&7f-jH9 zCi(v3aq?vre|df-{BaKWem_t5EAT_Ghg9Ip{3qU@SUe)1G zZE8t}lF)cbEoIriU)s+V_UF%4vtJLRoA^W9REor(z?bbmz0dr9IKR#P5+k4d)Yifm z>lr!z#_d=8es`gb|IT)mJl9L2f9k(t@(RD-zVpzooJ85&-|u&2OGY$A1D0v@We zzS!TAorm-#OOK8ePajvd0|8TqJRkgXU&>!pLYZCBnFTN$?}UL0az43pbXCuM zBKWO*%CtXTJ^_A4fEDz`dTHO+HD*Q4Cu`821|FnxJ`wGmLVF~B^2uLgEPVO-BJi88 zQ}d@DW@F})!{glevOllbz;Ez+;r=9Cc|1*?VBw4BuN;p%H>&YBI1d_6RVP~UDDY+e zgOMmmG_G{)5Y8Xf^dRfCp4LlepK5T%Q73_dfjYANV<%bmi}{yq|JKcF_UoJr*?+Q8 z^+@~)eA)i%|H0qW`=yD6FVC+<{MAwXdverIB*cGo)9R6YBJgGYTYyiZaiwF2aQ_*M z4G6HaWmq=ber3hy990r2;C@#=`JqL1E1yK>r{?@b*k5~UV&4})TFeZv=Q?73vSR1w z#iQnv?3Px4Gv1zLA zS;r-LJY8&K!;kETnV&}!{2NbK<8N>Z6pwjrt#}ms8#4c=fls1wrDKP1{>Uc-gKWt- zwuU8LXxk&jQ73_dfjYANYuj1&i~S)vpL{t(&3>J8A^Sg$f-l=&;XZC4zt8o;FB7iJ zztU~-`S%N{@i#aD;$NwQJnE5p38ocUBiLJYa#?m#oKKW_LDPUewfE4dpGb&*&9uyj{{+6w ze|XQt-a|xA7@W#pZ@?;SyK#TG1-1VSR44!C@!N>s1C=wI-HtM;m}84QV-a76?=95A$u<>trljT2wFY}+OC+5FG#)gLdm(6B`wtPN6 zrp-|<8aPM(v+wu*nw}YX|4HzZb4lG?)BbqxKdr;x159P_-^6~bwBi77M~;`)wcPzJ z=aYQ2N8;!Gr-QXEe6b%U`?JUpHGk@1Hs*M|nB~To{n>c9|9E`8t_?r3|L&Zd1V^gz zH*EZy*R$f$E1%p4d=iZ-9XrIzC&PlQ*g3Y2C5>)-aHXS80tEwguIEzgBnq_un(EEqw93l;iL0J!Lhzw_(utJhK+a;*J=wltg%EB|h*oQvwrFj)l?rYO$bG zV-V`A5mE5vSl9&on8IU0MvG*kBl3y2@_Q!kgsQ`bQG{6>*PhvOfM<$Pnx>tM-?P=gZ zD%z9tN&UrUfB4xezOI)WU(P3em#Fzu2Uik`$EA0<@nwI$v($e)9_el2i+w4Xf9VI+ z_#0vNCjVA_;>Y70;FD-v>DVEBJRzUt1@T@Zy_a3?r{cREbrL8Ts3Yf-fjKt&J@QGx zLu&TxoD2Eyi}rOR@h5-QZ7Y8-`-`Uh2Co-xKjF&!Cw8#mOV0y=Ke4&mduImcLHthv zKZG)=c;1Nk6ZofqPoi<9V~2451&oafu-1E6PHz9--~HK9C6R)GN@V{cUYYo1cdXki z4`ov8d2a3%3x3%xO#5#^9a1k@i@%jyg}zG=M?RXi@#;mWBm3e>aM5s{RB#cwMsRr& zTu5T^pzAtR#(%Aog>TKn%x8zdf32mOuc;jp>dT+HxbbCQ|J2H?@A*IXwT=oh|0P{5 zd@)ZJxkm6`(ngKH!HE$6zrpJzhW{?^weZC{RrcS-ZEF0} z_?t4s|NP5!n=vYrO5Xz&{d1mwf@wdhkh+BP$5S@%=FQ2gX^1F3(WJXklf z$87DRICI^TFPQyb68DjMNg@6Ya|+wjy56Y1h17-|t3RVH4d+QkTXHV@8EuioYKN}t zP#M3$Aq(Hy<8tP*26)1&e5RM$A)&r{XLi#Fe}ONLg?I2bp<_ynvG8!d8($s^4^LD3 zPPD9wh%lgbn%zuT9jSKq^ zxh((u_#H8Aj&jk!IXRb{UEVa3ORQ&+Gnbs5XWAb(mn5-UQ}8#zKe@g8d(m=9(-l@< zi?-xk(saJr7k+ZdV-H*S;<+!!;$sWce5vz(q*$E%h#Oyy#mS5P$Ks8TS@>S_x*M0O z@h6{?5dXEn58;?p;LH5i0-r?VO2-c2V+r%3@j-UWd$gyMeb0c$9d!~Y7^oxXlHlW( z{bJrM`!D#Qn*BQGLiU$@!i_K6U-BXU{>xi!;fpnr%s+3L8h`3165{^}@IyEz75Fm$ zPk>LNaiwF2aQ_u1u?a!eX&cRpCjNL8U%5GI#w-}PO8&F&=~R8PX=G1FtZ~Jjj>x4| zSD5xEu&2`~z^35W#%{dg?)%@1x~G$m_B3!H746CSBp>aO_}SB$QDDQD_UME?Gagp+ zrygcw`t!niH@@u83y=Aa$K@L>e6i;x$K&$H)%Y7W{(((aJPLf7f8dG4;t}~|Vvyaj zh4ysXb_r$iW)glOUUELExH-Q46<4d-uX9E;9+#b)5{W1pW`eC(*dlu|qij0>&l<*v|H>P40;u zUnzebpBVt9mKJev;@9<$DUm%LF}_7U5&XJ-Y1)4a?j!XQtOFZYp}$WXM`kVfJ%u>P zzIYB?G@K_DTtu!BT%H3Ll2|JrW$ zxn^<@zlBD7I!h~j`?0HDoD&AF%l2t$o_9;x$$NDzrn6* zOo`#Y1#>KXdG0LuFBpS&SuS0=l<03g(%uCL@gI^uHL|B8@MZo(#+v;1;~r9%aQ+y} zQ{W`En$o?yJ zH@W7z}fs5$G;2{wFbA5h@GR8cKvbZQJj zG1F+G8()r@KY$-oV#NHYNfy4?Ta#lxy^WAAIx*qNuXe$jvOBir&#uj`Gsu%`Rmo}*Etume@E~9NKO>^vi%c_ zr1CpFUSjx9&$003`J;%xw14nhNyYo-TmFlDp3eL{m%#rI@JTeTbnFn$A35QkRMzwW QJ9pd8@{S6L6%17Se}dI!Qvd(} diff --git a/tests/goodixmoc/device b/tests/goodixmoc/device index 1e209a1d..4f96b612 100644 --- a/tests/goodixmoc/device +++ b/tests/goodixmoc/device @@ -1,31 +1,30 @@ -P: /devices/pci0000:00/0000:00:14.0/usb1/1-3 -N: bus/usb/001/023=12010002EF000040C627966400010102030109022000010103A0320904000002FF0000040705830240000007050102400000 -E: DEVNAME=/dev/bus/usb/001/023 +P: /devices/pci0000:00/0000:00:14.0/usb3/3-9 +N: bus/usb/003/004=12010002EF000040C627AC6300010102030109022000010103A0320904000002FF0000040705830240000007050102400000 +E: DEVNAME=/dev/bus/usb/003/004 E: DEVTYPE=usb_device E: DRIVER=usb -E: PRODUCT=27c6/6496/100 +E: PRODUCT=27c6/63ac/100 E: TYPE=239/0/0 -E: BUSNUM=001 -E: DEVNUM=023 +E: BUSNUM=003 +E: DEVNUM=004 E: MAJOR=189 -E: MINOR=22 +E: MINOR=259 E: SUBSYSTEM=usb E: ID_VENDOR=Goodix_Technology_Co.__Ltd. E: ID_VENDOR_ENC=Goodix\x20Technology\x20Co.\x2c\x20Ltd. E: ID_VENDOR_ID=27c6 E: ID_MODEL=Goodix_USB2.0_MISC E: ID_MODEL_ENC=Goodix\x20USB2.0\x20MISC -E: ID_MODEL_ID=6496 +E: ID_MODEL_ID=63ac E: ID_REVISION=0100 -E: ID_SERIAL=Goodix_Technology_Co.__Ltd._Goodix_USB2.0_MISC_XXXX_MOC_B0 -E: ID_SERIAL_SHORT=XXXX_MOC_B0 +E: ID_SERIAL=Goodix_Technology_Co.__Ltd._Goodix_USB2.0_MISC_UID4C77C784_XXXX_MOC_B0 +E: ID_SERIAL_SHORT=UID4C77C784_XXXX_MOC_B0 E: ID_BUS=usb E: ID_USB_INTERFACES=:ff0000: E: ID_VENDOR_FROM_DATABASE=Shenzhen Goodix Technology Co.,Ltd. E: ID_AUTOSUSPEND=1 -E: ID_PERSIST=0 -E: ID_PATH=pci-0000:00:14.0-usb-0:3 -E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_3 +E: ID_PATH=pci-0000:00:14.0-usb-0:9 +E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_9 A: authorized=1\n A: avoid_reset_quirk=0\n A: bConfigurationValue=1\n @@ -38,30 +37,34 @@ A: bNumConfigurations=1\n A: bNumInterfaces= 1\n A: bcdDevice=0100\n A: bmAttributes=a0\n -A: busnum=1\n -A: configuration=XXXX_MOC_B0\n -H: descriptors=12010002EF000040C627966400010102030109022000010103A0320904000002FF0000040705830240000007050102400000 -A: dev=189:22\n -A: devnum=23\n -A: devpath=3\n +A: busnum=3\n +A: configuration=UID4C77C784_XXXX_MOC_B0\n +H: descriptors=12010002EF000040C627AC6300010102030109022000010103A0320904000002FF0000040705830240000007050102400000 +A: dev=189:259\n +A: devnum=4\n +A: devpath=9\n L: driver=../../../../../bus/usb/drivers/usb -L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1c/device:1d/device:20 -A: idProduct=6496\n +L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:13/device:14/device:1f +A: idProduct=63ac\n A: idVendor=27c6\n A: ltm_capable=no\n A: manufacturer=Goodix Technology Co., Ltd.\n A: maxchild=0\n -L: port=../1-0:1.0/usb1-port3 -A: power/active_duration=22667\n +L: port=../3-0:1.0/usb3-port9 +A: power/active_duration=702588\n +A: power/async=enabled\n A: power/autosuspend=2\n A: power/autosuspend_delay_ms=2000\n -A: power/connected_duration=917616\n +A: power/connected_duration=78973756\n A: power/control=auto\n A: power/level=auto\n A: power/persist=1\n -A: power/runtime_active_time=22809\n +A: power/runtime_active_kids=0\n +A: power/runtime_active_time=707156\n +A: power/runtime_enabled=enabled\n A: power/runtime_status=active\n -A: power/runtime_suspended_time=894564\n +A: power/runtime_suspended_time=78265226\n +A: power/runtime_usage=0\n A: power/wakeup=disabled\n A: power/wakeup_abort_count=\n A: power/wakeup_active=\n @@ -73,34 +76,34 @@ A: power/wakeup_max_time_ms=\n A: power/wakeup_total_time_ms=\n A: product=Goodix USB2.0 MISC\n A: quirks=0x0\n -A: removable=removable\n +A: removable=fixed\n A: rx_lanes=1\n -A: serial=XXXX_MOC_B0\n +A: serial=UID4C77C784_XXXX_MOC_B0\n A: speed=12\n A: tx_lanes=1\n -A: urbnum=298\n +A: urbnum=5759\n A: version= 2.00\n -P: /devices/pci0000:00/0000:00:14.0/usb1 -N: bus/usb/001/001=12010002090001406B1D020017050302010109021900010100E0000904000001090000000705810304000C -E: DEVNAME=/dev/bus/usb/001/001 +P: /devices/pci0000:00/0000:00:14.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/517 +E: PRODUCT=1d6b/2/515 E: TYPE=9/0/1 -E: BUSNUM=001 +E: BUSNUM=003 E: DEVNUM=001 E: MAJOR=189 -E: MINOR=0 +E: MINOR=256 E: SUBSYSTEM=usb -E: ID_VENDOR=Linux_5.17.12-300.fc36.x86_64_xhci-hcd -E: ID_VENDOR_ENC=Linux\x205.17.12-300.fc36.x86_64\x20xhci-hcd +E: ID_VENDOR=Linux_5.15.0-57-generic_xhci-hcd +E: ID_VENDOR_ENC=Linux\x205.15.0-57-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=0517 -E: ID_SERIAL=Linux_5.17.12-300.fc36.x86_64_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_REVISION=0515 +E: ID_SERIAL=Linux_5.15.0-57-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: @@ -123,31 +126,35 @@ A: bMaxPacketSize0=64\n A: bMaxPower=0mA\n A: bNumConfigurations=1\n A: bNumInterfaces= 1\n -A: bcdDevice=0517\n +A: bcdDevice=0515\n A: bmAttributes=e0\n -A: busnum=1\n -A: configuration=\n -H: descriptors=12010002090001406B1D020017050302010109021900010100E0000904000001090000000705810304000C -A: dev=189:0\n +A: busnum=3\n +A: configuration= +H: descriptors=12010002090001406B1D020015050302010109021900010100E0000904000001090000000705810304000C +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:1c/device:1d +L: firmware_node=../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:13/device:14 A: idProduct=0002\n A: idVendor=1d6b\n A: interface_authorized_default=1\n A: ltm_capable=no\n -A: manufacturer=Linux 5.17.12-300.fc36.x86_64 xhci-hcd\n +A: manufacturer=Linux 5.15.0-57-generic xhci-hcd\n A: maxchild=12\n -A: power/active_duration=164289796\n +A: power/active_duration=78971960\n +A: power/async=enabled\n A: power/autosuspend=0\n A: power/autosuspend_delay_ms=0\n -A: power/connected_duration=164360220\n +A: power/connected_duration=78974992\n A: power/control=auto\n A: power/level=auto\n -A: power/runtime_active_time=164331876\n +A: power/runtime_active_kids=2\n +A: power/runtime_active_time=78973899\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 @@ -164,48 +171,52 @@ A: rx_lanes=1\n A: serial=0000:00:14.0\n A: speed=480\n A: tx_lanes=1\n -A: urbnum=2097\n +A: urbnum=1824\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:9DED -E: PCI_SUBSYS_ID=17AA:2292 +E: PCI_ID=8086:51ED +E: PCI_SUBSYS_ID=1028:0B00 E: PCI_SLOT_NAME=0000:00:14.0 -E: MODALIAS=pci:v00008086d00009DEDsv000017AAsd00002292bc0Csc03i30 +E: MODALIAS=pci:v00008086d000051EDsv00001028sd00000B00bc0Csc03i30 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_AUTOSUSPEND=1 -E: ID_MODEL_FROM_DATABASE=Cannon Point-LP USB 3.1 xHCI Controller A: ari_enabled=0\n A: broken_parity_status=0\n A: class=0x0c0330\n -H: config=8680ED9D060490021130030C00008000040022EA000000000000000000000000000000000000000000000000AA179222000000007000000000000000FF010000FD0134808FC6FF8300000000000000007F6DDC0F00000000F507312600000000316000000000000000000000000000000180C2C1080000000000000000000000059087001803E0FE0000000000000000090014F01000400100000000C10A080000080E00001800008F40020000010000000000000000000008000000040000000000000000000000000000000000000000000000000000000800000004000000000000000000000000000000000000000000000000000000B50F320112000000 +H: config=8680ED51060490020130030C0000800004002A8F6200000000000000000000000000000000000000000000002810000B000000007000000000000000FF010000FD0134A089C27F8000000000000000003F6DD80F000000000000000000000000316000000000000000000000000000000180C2C1080000000000000000000000059087003808E0FE000000000000000009B014F01000400100000000C10A080000080E00001800008F50020000010000090000018680C00009001014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B50F100112000000 A: consistent_dma_mask_bits=64\n A: d3cold_allowed=1\n A: dbc=disabled\n -A: device=0x9ded\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:1c -A: irq=128\n -A: local_cpulist=0-7\n -A: local_cpus=ff\n -A: modalias=pci:v00008086d00009DEDsv000017AAsd00002292bc0Csc03i30\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:13 +L: iommu=../../virtual/iommu/dmar1 +L: iommu_group=../../../kernel/iommu_groups/13 +A: irq=167\n +A: local_cpulist=0-19\n +A: local_cpus=fffff\n +A: modalias=pci:v00008086d000051EDsv00001028sd00000B00bc0Csc03i30\n A: msi_bus=1\n -A: msi_irqs/128=msi\n +A: msi_irqs/167=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 11 12 2112 12\nxHCI ring segments 46 50 4096 50\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 6 32 128 1\nbuffer-32 0 0 32 0\n +A: pools=poolinfo - 0.1\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 13 14 2112 14\nxHCI ring segments 38 42 4096 42\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 3 32 128 1\nbuffer-32 0 0 32 0\n +A: power/async=enabled\n A: power/control=auto\n -A: power/runtime_active_time=164332777\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=78974886\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 @@ -216,9 +227,9 @@ 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=0x00000000ea220000 0x00000000ea22ffff 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=0x11\n -A: subsystem_device=0x2292\n -A: subsystem_vendor=0x17aa\n +A: resource=0x000000628f2a0000 0x000000628f2affff 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=0x0b00\n +A: subsystem_vendor=0x1028\n A: vendor=0x8086\n From 0bf7d58c5ea0c23b6d9371785af691e25f82a0f8 Mon Sep 17 00:00:00 2001 From: Enrik Berkhan Date: Thu, 19 Jan 2023 20:28:33 +0100 Subject: [PATCH 21/50] goodixmoc: cleanup enroll state machine As suggested by @benzea, the following cleanup actions have been performed: - let case order match enroll states enum - remove FP_ENROLL_IDENTIFY that is no longer used - finally use fpi_ssm_next_state instead of explicitly jumping to FP_ENROLL_CREATE Additionally, all types/functions/variables referring to "enroll_init" used for FP_ENROLL_CREATE have been renamed to match "enroll_create". All other states use similar name matching. Signed-off-by: Enrik Berkhan --- libfprint/drivers/goodixmoc/goodix.c | 32 +++++++++++----------- libfprint/drivers/goodixmoc/goodix.h | 1 - libfprint/drivers/goodixmoc/goodix_proto.c | 6 ++-- libfprint/drivers/goodixmoc/goodix_proto.h | 6 ++-- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index d8f11351..c4b718ce 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -631,20 +631,20 @@ fp_enroll_enum_cb (FpiDeviceGoodixMoc *self, return; } - fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_CREATE); + fpi_ssm_next_state (self->task_ssm); } static void -fp_enroll_init_cb (FpiDeviceGoodixMoc *self, - gxfp_cmd_response_t *resp, - GError *error) +fp_enroll_create_cb (FpiDeviceGoodixMoc *self, + gxfp_cmd_response_t *resp, + GError *error) { if (error) { fpi_ssm_mark_failed (self->task_ssm, error); return; } - memcpy (self->template_id, resp->enroll_init.tid, TEMPLATE_ID_SIZE); + memcpy (self->template_id, resp->enroll_create.tid, TEMPLATE_ID_SIZE); fpi_ssm_next_state (self->task_ssm); } @@ -837,16 +837,6 @@ fp_enroll_sm_run_state (FpiSsm *ssm, FpDevice *device) switch (fpi_ssm_get_cur_state (ssm)) { - case FP_ENROLL_ENUM: - { - goodix_sensor_cmd (self, MOC_CMD0_GETFINGERLIST, MOC_CMD1_DEFAULT, - false, - (const guint8 *) &dummy, - 1, - fp_enroll_enum_cb); - } - break; - case FP_ENROLL_PWR_BTN_SHIELD_ON: { goodix_sensor_cmd (self, MOC_CMD0_PWR_BTN_SHIELD, MOC_CMD1_PWR_BTN_SHIELD_ON, @@ -857,13 +847,23 @@ fp_enroll_sm_run_state (FpiSsm *ssm, FpDevice *device) } break; + case FP_ENROLL_ENUM: + { + goodix_sensor_cmd (self, MOC_CMD0_GETFINGERLIST, MOC_CMD1_DEFAULT, + false, + (const guint8 *) &dummy, + 1, + fp_enroll_enum_cb); + } + break; + case FP_ENROLL_CREATE: { goodix_sensor_cmd (self, MOC_CMD0_ENROLL_INIT, MOC_CMD1_DEFAULT, false, (const guint8 *) &dummy, 1, - fp_enroll_init_cb); + fp_enroll_create_cb); } break; diff --git a/libfprint/drivers/goodixmoc/goodix.h b/libfprint/drivers/goodixmoc/goodix.h index 56b2d171..4ec5511f 100644 --- a/libfprint/drivers/goodixmoc/goodix.h +++ b/libfprint/drivers/goodixmoc/goodix.h @@ -44,7 +44,6 @@ typedef enum { typedef enum { FP_ENROLL_PWR_BTN_SHIELD_ON = 0, FP_ENROLL_ENUM, - FP_ENROLL_IDENTIFY, FP_ENROLL_CREATE, FP_ENROLL_CAPTURE, FP_ENROLL_UPDATE, diff --git a/libfprint/drivers/goodixmoc/goodix_proto.c b/libfprint/drivers/goodixmoc/goodix_proto.c index 72511a88..d7713f0b 100644 --- a/libfprint/drivers/goodixmoc/goodix_proto.c +++ b/libfprint/drivers/goodixmoc/goodix_proto.c @@ -343,10 +343,10 @@ gx_proto_parse_body (uint16_t cmd, uint8_t *buffer, uint16_t buffer_len, pgxfp_c break; case MOC_CMD0_ENROLL_INIT: - if (buffer_len < sizeof (gxfp_enroll_init_t) + 1) + if (buffer_len < sizeof (gxfp_enroll_create_t) + 1) return -1; if (presp->result == GX_SUCCESS) - memcpy (&presp->enroll_init.tid, &buffer[1], TEMPLATE_ID_SIZE); + memcpy (&presp->enroll_create.tid, &buffer[1], TEMPLATE_ID_SIZE); break; case MOC_CMD0_ENROLL: @@ -465,4 +465,4 @@ gx_proto_init_sensor_config (pgxfp_sensor_cfg_t pconfig) memcpy (pconfig->crc_value, &crc32_calc, PACKAGE_CRC_SIZE); return 0; -} \ No newline at end of file +} diff --git a/libfprint/drivers/goodixmoc/goodix_proto.h b/libfprint/drivers/goodixmoc/goodix_proto.h index 000be2fe..b8ccdca8 100644 --- a/libfprint/drivers/goodixmoc/goodix_proto.h +++ b/libfprint/drivers/goodixmoc/goodix_proto.h @@ -105,10 +105,10 @@ typedef struct _gxfp_parse_msg } gxfp_parse_msg_t, *pgxfp_parse_msg_t; -typedef struct _gxfp_enroll_init +typedef struct _gxfp_enroll_create { uint8_t tid[TEMPLATE_ID_SIZE]; -} gxfp_enroll_init_t, *pgxfp_enroll_init_t; +} gxfp_enroll_create_t, *pgxfp_enroll_create_t; #pragma pack(push, 1) typedef struct _template_format @@ -192,7 +192,7 @@ typedef struct _fp_cmd_response { gxfp_parse_msg_t parse_msg; gxfp_verify_t verify; - gxfp_enroll_init_t enroll_init; + gxfp_enroll_create_t enroll_create; gxfp_capturedata_t capture_data_resp; gxfp_check_duplicate_t check_duplicate_resp; gxfp_enroll_commit_t enroll_commit; From 22683ec4902afaaa62b9fbcaadfeff29ab8e8b6d Mon Sep 17 00:00:00 2001 From: "wagner.oliveira" Date: Fri, 27 Jan 2023 18:23:55 -0500 Subject: [PATCH 22/50] Fix AES4000 wrong IDs (swapped pid/vid) --- data/autosuspend.hwdb | 2 +- libfprint/drivers/aes4000.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index d476f7e1..858b481b 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -67,7 +67,7 @@ usb:v08FFp5731* ID_PERSIST=0 # Supported by libfprint driver aes4000 -usb:v5501p08FF* +usb:v08FFp5501* ID_AUTOSUSPEND=1 ID_PERSIST=0 diff --git a/libfprint/drivers/aes4000.c b/libfprint/drivers/aes4000.c index e5770b9e..d810b3e4 100644 --- a/libfprint/drivers/aes4000.c +++ b/libfprint/drivers/aes4000.c @@ -124,7 +124,7 @@ G_DEFINE_TYPE (FpiDeviceAes4000, fpi_device_aes4000, FPI_TYPE_DEVICE_AES3K); static const FpIdEntry id_table[] = { - { .pid = 0x08ff, .vid = 0x5501 }, + { .vid = 0x08ff, .pid = 0x5501 }, { .vid = 0, .pid = 0, .driver_data = 0 }, }; From bb5feeca777f763d3371394a0773051f8486b0df Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Wed, 15 Mar 2023 13:33:52 +0100 Subject: [PATCH 23/50] ci: Use detached pipelines See https://gitlab.freedesktop.org/freedesktop/freedesktop/-/issues/438#what-it-means-for-me-a-maintainer-of-a-project-part-of-gitlabfreedesktoporg --- .gitlab-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 278d379a..19ef0dbb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,6 +15,11 @@ variables: BUNDLE: "org.freedesktop.libfprint.Demo.flatpak" LAST_ABI_BREAK: "056ea541ddc97f5806cffbd99a12dc87e4da3546" +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + - if: $CI_PIPELINE_SOURCE == 'push' + stages: - image-build - check-source From 677c50fc516b21d91888ccfd498c918d5014e8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 15 Mar 2023 14:50:41 +0100 Subject: [PATCH 24/50] fprint-list-supported-devices: Do not double-free usb_list We were leaking the spi_list instead. --- libfprint/fprint-list-supported-devices.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libfprint/fprint-list-supported-devices.c b/libfprint/fprint-list-supported-devices.c index 132a72f0..ace5de09 100644 --- a/libfprint/fprint-list-supported-devices.c +++ b/libfprint/fprint-list-supported-devices.c @@ -132,7 +132,7 @@ main (int argc, char **argv) g_print ("%s", (char *) l->data); g_print ("\n"); - g_list_free_full (usb_list, g_free); + g_list_free_full (g_steal_pointer (&usb_list), g_free); g_print ("## SPI devices\n"); g_print ("\n"); @@ -146,7 +146,7 @@ main (int argc, char **argv) g_print ("%s", (char *) l->data); g_print ("\n"); - g_list_free_full (usb_list, g_free); + g_list_free_full (g_steal_pointer (&spi_list), g_free); g_hash_table_destroy (printed); From e7ca05e1bfac4d4068be56b2c3e873c9eb382baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 15 Mar 2023 15:07:37 +0100 Subject: [PATCH 25/50] ci: Use proper name for valgrind junit file --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 19ef0dbb..6991a48b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -100,7 +100,7 @@ test_valgrind: - meson test -C _build --print-errorlogs --no-stdsplit --setup=valgrind artifacts: reports: - junit: "_build/meson-logs/testlog.junit.xml" + junit: "_build/meson-logs/testlog-valgrind.junit.xml" expose_as: 'Valgrind test logs' when: always paths: From 5e98f10011e89488aa8d73842e22dd0a7bd47e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 15 Mar 2023 16:16:37 +0100 Subject: [PATCH 26/50] ci: Do not require coverage to always happen meson coverage could be broken in some cases, so let's ignore the error for now if it happens. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6991a48b..7991dedb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -73,8 +73,8 @@ test: - meson --werror -Ddrivers=all -Db_coverage=true . _build - ninja -C _build - meson test -C _build --print-errorlogs --no-stdsplit --timeout-multiplier 3 - - ninja -C _build coverage - - cat _build/meson-logs/coverage.txt + - ninja -C _build coverage || true # FIXME: always enable this once meson is fixed + - cat _build/meson-logs/coverage.txt || true artifacts: reports: junit: "_build/meson-logs/testlog.junit.xml" From b6223a9d0a26542adecac785f656f1ccc0b66852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 15 Mar 2023 15:07:00 +0100 Subject: [PATCH 27/50] test-fpi-device: Only push pragma -wdanging-pointer in newer GCC versions It may not be supported and warn otherwise --- tests/test-fpi-device.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test-fpi-device.c b/tests/test-fpi-device.c index f778eaa8..33013bc7 100644 --- a/tests/test-fpi-device.c +++ b/tests/test-fpi-device.c @@ -30,8 +30,10 @@ #include "fp-print-private.h" /* gcc 12.0.1 is complaining about dangling pointers in the auto_close* functions */ +#if G_GNUC_CHECK_VERSION (12, 0) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdangling-pointer" +#endif /* Utility functions */ @@ -69,7 +71,9 @@ auto_close_fake_device_free (FpAutoCloseDevice *device) } G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpAutoCloseDevice, auto_close_fake_device_free) +#if G_GNUC_CHECK_VERSION (12, 0) #pragma GCC diagnostic pop +#endif typedef FpDeviceClass FpAutoResetClass; static FpAutoResetClass default_fake_dev_class = {0}; From 9fd2ccff2916ac2131973b84736c3f374f7c05d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 15 Mar 2023 16:03:58 +0100 Subject: [PATCH 28/50] virtual-device-listener: Always return task even if we got zero bytes See: https://gitlab.gnome.org/GNOME/glib/-/issues/1346 --- libfprint/drivers/virtual-device-listener.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/virtual-device-listener.c b/libfprint/drivers/virtual-device-listener.c index cd2fe2e0..16987a1b 100644 --- a/libfprint/drivers/virtual-device-listener.c +++ b/libfprint/drivers/virtual-device-listener.c @@ -273,7 +273,8 @@ on_stream_read_cb (GObject *source_object, } else { - // g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Got empty data"); + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Got empty data"); return; } } From 6ed1b707d508277b414bfca7fd7dd24ee532b953 Mon Sep 17 00:00:00 2001 From: swbgdx Date: Tue, 27 Dec 2022 14:56:52 +0800 Subject: [PATCH 29/50] goodixmoc: Add PID 0x6092 --- 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 858b481b..768f98ce 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -169,6 +169,7 @@ usb:v10A5pD205* # Supported by libfprint driver goodixmoc usb:v27C6p5840* usb:v27C6p6014* +usb:v27C6p6092* usb:v27C6p6094* usb:v27C6p609C* usb:v27C6p60A2* diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index c4b718ce..1e731f18 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -1360,6 +1360,7 @@ gx_fp_probe (FpDevice *device) case 0x6496: case 0x60A2: case 0x6014: + case 0x6092: case 0x6094: case 0x609C: case 0x631C: @@ -1602,6 +1603,7 @@ fpi_device_goodixmoc_init (FpiDeviceGoodixMoc *self) static const FpIdEntry id_table[] = { { .vid = 0x27c6, .pid = 0x5840, }, { .vid = 0x27c6, .pid = 0x6014, }, + { .vid = 0x27c6, .pid = 0x6092, }, { .vid = 0x27c6, .pid = 0x6094, }, { .vid = 0x27c6, .pid = 0x609C, }, { .vid = 0x27c6, .pid = 0x60A2, }, From 682fce6a5b49841dbfa0867765417ef3ab574a31 Mon Sep 17 00:00:00 2001 From: Elytscha Smith Date: Fri, 9 Dec 2022 16:16:01 +0100 Subject: [PATCH 30/50] goodixmoc: add PID 0x60BC --- 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 768f98ce..b04f5ee4 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -173,6 +173,7 @@ usb:v27C6p6092* usb:v27C6p6094* usb:v27C6p609C* usb:v27C6p60A2* +usb:v27C6p60BC* usb:v27C6p631C* usb:v27C6p634C* usb:v27C6p6384* diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index 1e731f18..cb802eae 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -1363,6 +1363,7 @@ gx_fp_probe (FpDevice *device) case 0x6092: case 0x6094: case 0x609C: + case 0x60BC: case 0x631C: case 0x634C: case 0x6384: @@ -1607,6 +1608,7 @@ static const FpIdEntry id_table[] = { { .vid = 0x27c6, .pid = 0x6094, }, { .vid = 0x27c6, .pid = 0x609C, }, { .vid = 0x27c6, .pid = 0x60A2, }, + { .vid = 0x27c6, .pid = 0x60BC, }, { .vid = 0x27c6, .pid = 0x631C, }, { .vid = 0x27c6, .pid = 0x634C, }, { .vid = 0x27c6, .pid = 0x6384, }, From c782298ae49be33bc271476c4136cf8732b44436 Mon Sep 17 00:00:00 2001 From: haoweilo Date: Fri, 28 Oct 2022 15:47:43 +0800 Subject: [PATCH 31/50] fpcmoc: Ensure the current SSM is never overwritten --- libfprint/drivers/fpcmoc/fpc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/fpcmoc/fpc.c b/libfprint/drivers/fpcmoc/fpc.c index 0f2b5591..dd6bf090 100644 --- a/libfprint/drivers/fpcmoc/fpc.c +++ b/libfprint/drivers/fpcmoc/fpc.c @@ -132,7 +132,11 @@ fpc_cmd_receive_cb (FpiUsbTransfer *transfer, } ssm_state = fpi_ssm_get_cur_state (transfer->ssm); - fp_dbg ("%s current ssm state: %d", G_STRFUNC, ssm_state); + fp_dbg ("%s current ssm request: %d state: %d", G_STRFUNC, data->request, ssm_state); + + /* clean cmd_ssm except capture command for suspend/resume case */ + if (ssm_state != FP_CMD_SEND || data->request != FPC_CMD_ARM) + self->cmd_ssm = NULL; if (data->cmdtype == FPC_CMDTYPE_TO_DEVICE) { @@ -358,6 +362,7 @@ fpc_sensor_cmd (FpiDeviceFpcMoc *self, g_clear_object (&self->interrupt_cancellable); } + g_assert (self->cmd_ssm == NULL); self->cmd_ssm = fpi_ssm_new (FP_DEVICE (self), fpc_cmd_run_state, FP_CMD_NUM_STATES); From 9546659c15625713823d3786efb21d2fe24fc855 Mon Sep 17 00:00:00 2001 From: Egor Ignatov Date: Tue, 23 May 2023 16:06:01 +0300 Subject: [PATCH 32/50] goodixmoc: add PID 0x60A4 Signed-off-by: Egor Ignatov --- 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 b04f5ee4..39ace7b9 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -173,6 +173,7 @@ usb:v27C6p6092* usb:v27C6p6094* usb:v27C6p609C* usb:v27C6p60A2* +usb:v27C6p60A4* usb:v27C6p60BC* usb:v27C6p631C* usb:v27C6p634C* diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index cb802eae..2b71f2b9 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -1359,6 +1359,7 @@ gx_fp_probe (FpDevice *device) { case 0x6496: case 0x60A2: + case 0x60A4: case 0x6014: case 0x6092: case 0x6094: @@ -1608,6 +1609,7 @@ static const FpIdEntry id_table[] = { { .vid = 0x27c6, .pid = 0x6094, }, { .vid = 0x27c6, .pid = 0x609C, }, { .vid = 0x27c6, .pid = 0x60A2, }, + { .vid = 0x27c6, .pid = 0x60A4, }, { .vid = 0x27c6, .pid = 0x60BC, }, { .vid = 0x27c6, .pid = 0x631C, }, { .vid = 0x27c6, .pid = 0x634C, }, From 1c589336a23fd0cefd75ea1c36964c912a9b0e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 20 Apr 2023 17:41:56 +0200 Subject: [PATCH 33/50] meson: Remove duplicate gnome imports --- meson.build | 3 --- 1 file changed, 3 deletions(-) diff --git a/meson.build b/meson.build index 1badb164..848b035c 100644 --- a/meson.build +++ b/meson.build @@ -286,8 +286,6 @@ else endif if get_option('gtk-examples') - gnome = import('gnome') - gtk_dep = dependency('gtk+-3.0', required: false) if not gtk_dep.found() error('GTK+ 3.x is required for GTK+ examples') @@ -301,7 +299,6 @@ configure_file(output: 'config.h', configuration: libfprint_conf) subdir('examples') if get_option('doc') - gnome = import('gnome') subdir('doc') endif if get_option('gtk-examples') From e48d2b467a68416a792630f83afcb01bf0b3b5c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 22 Jun 2023 00:10:12 -0400 Subject: [PATCH 34/50] tests/virtual-device: Restore default enroll steps on tearDown When running the tests as a single script we may leave the device in an inconsistent state, so reset the enroll steps when done. --- tests/virtual-device.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/virtual-device.py b/tests/virtual-device.py index 4dc8e56e..2d022475 100644 --- a/tests/virtual-device.py +++ b/tests/virtual-device.py @@ -59,6 +59,8 @@ class GLibErrorMessage: class VirtualDeviceBase(unittest.TestCase): + DEFAULT_ENROLL_STEPS = 5 + @classmethod def setUpClass(cls): unittest.TestCase.setUpClass() @@ -101,6 +103,7 @@ class VirtualDeviceBase(unittest.TestCase): def tearDown(self): if self._close_on_teardown: self.assertTrue(self.dev.is_open()) + self.send_command('SET_ENROLL_STAGES', self.DEFAULT_ENROLL_STEPS) self.dev.close_sync() self.assertFalse(self.dev.is_open()) super().tearDown() @@ -339,7 +342,8 @@ class VirtualDevice(VirtualDeviceBase): self.assertEqual(self.dev.get_name(), 'Virtual device for debugging') self.assertTrue(self.dev.is_open()) self.assertEqual(self.dev.get_scan_type(), FPrint.ScanType.SWIPE) - self.assertEqual(self.dev.get_nr_enroll_stages(), 5) + self.assertEqual(self.dev.get_nr_enroll_stages(), + self.DEFAULT_ENROLL_STEPS) self.assertFalse(self.dev.supports_identify()) self.assertFalse(self.dev.supports_capture()) self.assertFalse(self.dev.has_storage()) From 28579239a6220b8d34048a9516ae89391bf8103f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 22 Jun 2023 00:12:14 -0400 Subject: [PATCH 35/50] tests/virtual-device: Reset keep alive status when test is done We don't want this to be preserved across tests, since it's meant to be used inside a single test case. --- tests/virtual-device.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/virtual-device.py b/tests/virtual-device.py index 2d022475..0e5ae942 100644 --- a/tests/virtual-device.py +++ b/tests/virtual-device.py @@ -103,6 +103,7 @@ class VirtualDeviceBase(unittest.TestCase): def tearDown(self): if self._close_on_teardown: self.assertTrue(self.dev.is_open()) + self.set_keep_alive(False) self.send_command('SET_ENROLL_STAGES', self.DEFAULT_ENROLL_STEPS) self.dev.close_sync() self.assertFalse(self.dev.is_open()) From 0819df6988db2b9a439e93f8da584a4ddcffe47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 22 Jun 2023 00:14:30 -0400 Subject: [PATCH 36/50] virtual-device: Reset transient parameters on deinit Tests may change some options so we need to reset these values, even though some may need not to be reserved when not in keep-alive mode. --- libfprint/drivers/virtual-device.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/virtual-device.c b/libfprint/drivers/virtual-device.c index c75455ff..17a7ad01 100644 --- a/libfprint/drivers/virtual-device.c +++ b/libfprint/drivers/virtual-device.c @@ -733,7 +733,13 @@ dev_deinit (FpDevice *dev) } if (!self->keep_alive) - stop_listener (self); + { + stop_listener (self); + self->supports_cancellation = TRUE; + } + + self->enroll_stages_passed = 0; + self->match_reported = FALSE; fpi_device_close_complete (dev, NULL); } From c27d72e3a1c7749725bf90e1d40c7495542c7184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 22 Jun 2023 00:26:08 -0400 Subject: [PATCH 37/50] tests/virtual-device: Do not preserve context on unplug tests The unplug tests assume that the device is removed from the context once they run, however we may have left the device around in a removed state causing other tests to fail. So isolate them in a new class where we create context and device instances every time we run a test case. --- tests/virtual-device.py | 172 +++++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 74 deletions(-) diff --git a/tests/virtual-device.py b/tests/virtual-device.py index 0e5ae942..f6e88ab8 100644 --- a/tests/virtual-device.py +++ b/tests/virtual-device.py @@ -60,6 +60,15 @@ class GLibErrorMessage: class VirtualDeviceBase(unittest.TestCase): DEFAULT_ENROLL_STEPS = 5 + USE_CLASS_DEVICE = True + + @classmethod + def get_device(cls, ctx): + for dev in ctx.get_devices(): + # We might have a USB device in the test system that needs skipping + if dev.get_driver() == cls._driver_name: + return dev + return None @classmethod def setUpClass(cls): @@ -75,26 +84,28 @@ class VirtualDeviceBase(unittest.TestCase): cls.sockaddr = os.path.join(cls.tmpdir, '{}.socket'.format(sock_name)) os.environ['FP_{}'.format(driver_name.upper())] = cls.sockaddr - cls.ctx = FPrint.Context() + cls._driver_name = driver_name - cls.dev = None - for dev in cls.ctx.get_devices(): - # We might have a USB device in the test system that needs skipping - if dev.get_driver() == driver_name: - cls.dev = dev - break - - assert cls.dev is not None, "You need to compile with {} for testing".format(driver_name) + if cls.USE_CLASS_DEVICE: + cls.ctx = FPrint.Context() + cls.dev = cls.get_device(cls.ctx) + assert cls.dev is not None, "You need to compile with {} for testing".format( + driver_name) @classmethod def tearDownClass(cls): shutil.rmtree(cls.tmpdir) - del cls.dev - del cls.ctx + if cls.USE_CLASS_DEVICE: + del cls.dev + del cls.ctx unittest.TestCase.tearDownClass() def setUp(self): super().setUp() + if not self.USE_CLASS_DEVICE: + self.ctx = FPrint.Context() + self.dev = self.get_device(self.ctx) + self.assertIsNotNone(self.dev) self._close_on_teardown = True self.assertFalse(self.dev.is_open()) self.dev.open_sync() @@ -107,6 +118,9 @@ class VirtualDeviceBase(unittest.TestCase): self.send_command('SET_ENROLL_STAGES', self.DEFAULT_ENROLL_STEPS) self.dev.close_sync() self.assertFalse(self.dev.is_open()) + if not self.USE_CLASS_DEVICE: + del self.dev + del self.ctx super().tearDown() def wait_timeout(self, interval): @@ -335,6 +349,79 @@ class VirtualDeviceBase(unittest.TestCase): self.assertEqual(self._verify_fp.props.fpi_data.get_string(), scan_nick) +class VirtualDeviceUnplugging(VirtualDeviceBase): + + driver_name = 'virtual_device' + USE_CLASS_DEVICE = False + + def test_device_unplug(self): + self._close_on_teardown = False + notified_spec = None + def on_removed_notify(dev, spec): + nonlocal notified_spec + notified_spec = spec + + removed = False + def on_removed(dev): + nonlocal removed + removed = dev.props.removed + + self.assertFalse(self.dev.props.removed) + + self.dev.connect('notify::removed', on_removed_notify) + self.dev.connect('removed', on_removed) + self.send_command('UNPLUG') + self.assertEqual(notified_spec.name, 'removed') + self.assertTrue(self.dev.props.removed) + self.assertIn(self.dev, self.ctx.get_devices()) + self.assertTrue(removed) + + with self.assertRaises(GLib.GError) as error: + self.dev.close_sync() + self.assertTrue(error.exception.matches(FPrint.DeviceError.quark(), + FPrint.DeviceError.REMOVED)) + + def test_device_unplug_during_verify(self): + self._close_on_teardown = False + self._destroy_on_teardown = True + + notified_spec = None + def on_removed_notify(dev, spec): + nonlocal notified_spec + notified_spec = spec + + removed = False + def on_removed(dev): + nonlocal removed + removed = dev.props.removed + + self.assertFalse(self.dev.props.removed) + self.dev.connect('notify::removed', on_removed_notify) + self.dev.connect('removed', on_removed) + + self.start_verify(FPrint.Print.new(self.dev), + identify=self.dev.supports_identify()) + + self.send_command('UNPLUG') + self.assertEqual(notified_spec.name, 'removed') + self.assertTrue(self.dev.props.removed) + self.assertIn(self.dev, self.ctx.get_devices()) + self.assertFalse(removed) + + with self.assertRaises(GLib.GError) as error: + self.complete_verify() + self.assertTrue(error.exception.matches(FPrint.DeviceError.quark(), + FPrint.DeviceError.REMOVED)) + + self.assertTrue(removed) + self.assertIn(self.dev, self.ctx.get_devices()) + + with self.assertRaises(GLib.GError) as error: + self.dev.close_sync() + self.assertTrue(error.exception.matches(FPrint.DeviceError.quark(), + FPrint.DeviceError.REMOVED)) + + class VirtualDevice(VirtualDeviceBase): def test_device_properties(self): @@ -705,69 +792,6 @@ class VirtualDevice(VirtualDeviceBase): self.assertEqual(self.dev.get_scan_type(), FPrint.ScanType.SWIPE) self.assertIsNone(notified_spec) - def test_device_unplug(self): - self._close_on_teardown = False - notified_spec = None - def on_removed_notify(dev, spec): - nonlocal notified_spec - notified_spec = spec - - removed = False - def on_removed(dev): - nonlocal removed - removed = True - - self.assertFalse(self.dev.props.removed) - - self.dev.connect('notify::removed', on_removed_notify) - self.dev.connect('removed', on_removed) - self.send_command('UNPLUG') - self.assertEqual(notified_spec.name, 'removed') - self.assertTrue(self.dev.props.removed) - self.assertTrue(removed) - - with self.assertRaises(GLib.GError) as error: - self.dev.close_sync() - self.assertTrue(error.exception.matches(FPrint.DeviceError.quark(), - FPrint.DeviceError.REMOVED)) - - def test_device_unplug_during_verify(self): - self._close_on_teardown = False - - notified_spec = None - def on_removed_notify(dev, spec): - nonlocal notified_spec - notified_spec = spec - - removed = False - def on_removed(dev): - nonlocal removed - removed = True - - self.assertFalse(self.dev.props.removed) - self.dev.connect('notify::removed', on_removed_notify) - self.dev.connect('removed', on_removed) - - self.start_verify(FPrint.Print.new(self.dev), - identify=self.dev.supports_identify()) - - self.send_command('UNPLUG') - self.assertEqual(notified_spec.name, 'removed') - self.assertTrue(self.dev.props.removed) - self.assertFalse(removed) - - with self.assertRaises(GLib.GError) as error: - self.complete_verify() - self.assertTrue(error.exception.matches(FPrint.DeviceError.quark(), - FPrint.DeviceError.REMOVED)) - - self.assertTrue(removed) - - with self.assertRaises(GLib.GError) as error: - self.dev.close_sync() - self.assertTrue(error.exception.matches(FPrint.DeviceError.quark(), - FPrint.DeviceError.REMOVED)) - def test_device_sleep(self): self.send_sleep(1500) From 4d96a3efaa57689f336f90896720bce887f3a48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 22 Jun 2023 00:34:05 -0400 Subject: [PATCH 38/50] tests/virtual-device: Check that removed devices are not in context anymore --- tests/virtual-device.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/virtual-device.py b/tests/virtual-device.py index f6e88ab8..46981e01 100644 --- a/tests/virtual-device.py +++ b/tests/virtual-device.py @@ -362,13 +362,22 @@ class VirtualDeviceUnplugging(VirtualDeviceBase): notified_spec = spec removed = False + ctx_removed = False + + def on_ctx_removed(ctx, dev): + nonlocal ctx_removed + ctx_removed = dev == self.dev + self.assertEqual(removed, ctx_removed) + def on_removed(dev): nonlocal removed removed = dev.props.removed + self.assertNotEqual(removed, ctx_removed) self.assertFalse(self.dev.props.removed) self.dev.connect('notify::removed', on_removed_notify) + self.ctx.connect('device-removed', on_ctx_removed) self.dev.connect('removed', on_removed) self.send_command('UNPLUG') self.assertEqual(notified_spec.name, 'removed') @@ -381,6 +390,11 @@ class VirtualDeviceUnplugging(VirtualDeviceBase): self.assertTrue(error.exception.matches(FPrint.DeviceError.quark(), FPrint.DeviceError.REMOVED)) + while not ctx_removed: + ctx.iteration(True) + + self.assertNotIn(self.dev, self.ctx.get_devices()) + def test_device_unplug_during_verify(self): self._close_on_teardown = False self._destroy_on_teardown = True @@ -391,12 +405,21 @@ class VirtualDeviceUnplugging(VirtualDeviceBase): notified_spec = spec removed = False + ctx_removed = False + + def on_ctx_removed(ctx, dev): + nonlocal ctx_removed + ctx_removed = dev == self.dev + self.assertEqual(removed, ctx_removed) + def on_removed(dev): nonlocal removed removed = dev.props.removed + self.assertNotEqual(removed, ctx_removed) self.assertFalse(self.dev.props.removed) self.dev.connect('notify::removed', on_removed_notify) + self.ctx.connect('device-removed', on_ctx_removed) self.dev.connect('removed', on_removed) self.start_verify(FPrint.Print.new(self.dev), @@ -421,6 +444,11 @@ class VirtualDeviceUnplugging(VirtualDeviceBase): self.assertTrue(error.exception.matches(FPrint.DeviceError.quark(), FPrint.DeviceError.REMOVED)) + while not ctx_removed: + ctx.iteration(True) + + self.assertNotIn(self.dev, self.ctx.get_devices()) + class VirtualDevice(VirtualDeviceBase): From 5e0bf2446beb29978bf3a3dbe138d2e0b8924e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 23 Jun 2023 13:26:04 -0400 Subject: [PATCH 39/50] meson: Bump requirements to 0.56 and adapt deprecated functions --- doc/meson.build | 2 +- libfprint/meson.build | 4 ++-- meson.build | 2 +- tests/meson.build | 22 +++++++++++----------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/meson.build b/doc/meson.build index 1a45aad8..77236a06 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -25,7 +25,7 @@ docpath = join_paths(get_option('datadir'), 'gtk-doc', 'html') gnome.gtkdoc(versioned_libname, main_xml: 'libfprint-docs.xml', - src_dir: join_paths(meson.source_root(), 'libfprint'), + src_dir: join_paths(meson.project_source_root(), 'libfprint'), include_directories: include_directories('../libfprint'), dependencies: libfprint_dep, content_files: content_files, diff --git a/libfprint/meson.build b/libfprint/meson.build index d3c8b034..7e3b771f 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -263,7 +263,7 @@ libfprint_drivers = static_library('fprint-drivers', install: false) mapfile = files('libfprint.ver') -vflag = '-Wl,--version-script,@0@/@1@'.format(meson.source_root(), mapfile[0]) +vflag = '-Wl,--version-script,@0@/@1@'.format(meson.project_source_root(), mapfile[0]) libfprint = shared_library(versioned_libname.split('lib')[1], sources: [ @@ -339,7 +339,7 @@ sync_udev_udb = custom_target('sync-udev-hwdb', command: [ 'cp', '-v', udev_hwdb_generator.full_path(), - meson.source_root() / 'data' + meson.project_source_root() / 'data' ] ) diff --git a/meson.build b/meson.build index 848b035c..d667b1af 100644 --- a/meson.build +++ b/meson.build @@ -6,7 +6,7 @@ project('libfprint', [ 'c', 'cpp' ], 'warning_level=1', 'c_std=gnu99', ], - meson_version: '>= 0.49.0') + meson_version: '>= 0.56.0') gnome = import('gnome') diff --git a/tests/meson.build b/tests/meson.build index a8761750..894add64 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -4,9 +4,9 @@ envs.set('G_DEBUG', 'fatal-warnings') envs.set('G_MESSAGES_DEBUG', 'all') # Setup paths -envs.set('MESON_SOURCE_ROOT', meson.source_root()) -envs.set('MESON_BUILD_ROOT', meson.build_root()) -envs.prepend('LD_LIBRARY_PATH', join_paths(meson.build_root(), 'libfprint')) +envs.set('MESON_SOURCE_ROOT', meson.project_source_root()) +envs.set('MESON_BUILD_ROOT', meson.project_build_root()) +envs.prepend('LD_LIBRARY_PATH', meson.project_build_root() / 'libfprint') # Set FP_DEVICE_EMULATION so that drivers can adapt (e.g. to use fixed # random numbers rather than proper ones) @@ -45,15 +45,15 @@ drivers_tests = [ if get_option('introspection') conf = configuration_data() - conf.set('SRCDIR', meson.source_root()) - conf.set('BUILDDIR', meson.build_root()) + conf.set('SRCDIR', meson.project_source_root()) + conf.set('BUILDDIR', meson.project_build_root()) configure_file(configuration: conf, input: 'create-driver-test.py.in', output: 'create-driver-test.py') endif if get_option('introspection') - envs.prepend('GI_TYPELIB_PATH', join_paths(meson.build_root(), 'libfprint')) + envs.prepend('GI_TYPELIB_PATH', meson.project_build_root() / 'libfprint') virtual_devices_tests = [ 'virtual-image', 'virtual-device', @@ -165,7 +165,7 @@ endif unit_tests_deps = { 'fpi-assembling' : [cairo_dep] } test_config = configuration_data() -test_config.set_quoted('SOURCE_ROOT', meson.source_root()) +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 @@ -218,7 +218,7 @@ test('udev-hwdb', gdb = find_program('gdb', required: false) if gdb.found() libfprint_wrapper = [ - gdb.path(), + gdb.full_path(), '-batch', '-ex', 'run', '--args', @@ -235,12 +235,12 @@ valgrind = find_program('valgrind', required: false) if valgrind.found() glib_share = glib_dep.get_pkgconfig_variable('prefix') / 'share' / glib_dep.name() glib_suppressions = glib_share + '/valgrind/glib.supp' - libfprint_suppressions = '@0@/@1@'.format(meson.source_root(), + libfprint_suppressions = '@0@/@1@'.format(meson.project_source_root(), files('libfprint.supp')[0]) - python_suppressions = '@0@/@1@'.format(meson.source_root(), + python_suppressions = '@0@/@1@'.format(meson.project_source_root(), files('valgrind-python.supp')[0]) libfprint_wrapper = [ - valgrind.path(), + valgrind.full_path(), '--tool=memcheck', '--leak-check=full', '--leak-resolution=high', From f49879b522a660fd75b10b200025e68118db2c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 23 Jun 2023 13:36:53 -0400 Subject: [PATCH 40/50] tests/virtual-image: Support passing specific FP_PRINTS_PATH env variable --- tests/meson.build | 2 ++ tests/virtual-image.py | 15 ++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index 894add64..4249ed1e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -19,6 +19,8 @@ envs.set('FP_DRIVERS_WHITELIST', ':'.join([ 'virtual_device_storage', ])) +envs.set('FP_PRINTS_PATH', meson.project_source_root() / 'examples' / 'prints') + envs.set('NO_AT_BRIDGE', '1') drivers_tests = [ diff --git a/tests/virtual-image.py b/tests/virtual-image.py index 448c4bc1..b3dc746e 100755 --- a/tests/virtual-image.py +++ b/tests/virtual-image.py @@ -46,12 +46,15 @@ def load_image(img): return img -if hasattr(os.environ, 'MESON_SOURCE_ROOT'): - root = os.environ['MESON_SOURCE_ROOT'] +if 'FP_PRINTS_PATH' in os.environ: + prints_path = os.environ['FP_PRINTS_PATH'] else: - root = os.path.join(os.path.dirname(__file__), '..') + if 'MESON_SOURCE_ROOT' in os.environ: + root = os.environ['MESON_SOURCE_ROOT'] + else: + root = os.path.join(os.path.dirname(__file__), '..') -imgdir = os.path.join(root, 'examples', 'prints') + prints_path = os.path.join(root, 'examples', 'prints') ctx = GLib.main_context_default() @@ -76,10 +79,12 @@ class VirtualImage(unittest.TestCase): assert cls.dev is not None, "You need to compile with virtual_image for testing" cls.prints = {} - for f in glob.glob(os.path.join(imgdir, '*.png')): + for f in glob.glob(os.path.join(prints_path, '*.png')): n = os.path.basename(f)[:-4] cls.prints[n] = load_image(f) + assert cls.prints, "No prints found in " + prints_path + @classmethod def tearDownClass(cls): shutil.rmtree(cls.tmpdir) From 8e702012fd0d8e40f64d096005ade18fb9e7208b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 28 Jun 2023 15:39:17 +0200 Subject: [PATCH 41/50] ci: Use debuginfo-install to install debug symbols dnf doesn't seem to handle the plugin well in rawhide: bash-5.2# dnf -y debuginfo-install glib2 Unknown argument "debuginfo-install" for command "dnf5". Add "--help" for more information about the arguments. --- .gitlab-ci/libfprint-templates.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci/libfprint-templates.yaml b/.gitlab-ci/libfprint-templates.yaml index dc35f806..46e0d475 100644 --- a/.gitlab-ci/libfprint-templates.yaml +++ b/.gitlab-ci/libfprint-templates.yaml @@ -33,7 +33,8 @@ diffutils LIBFPRINT_EXEC: | - dnf debuginfo-install -y \ + dnf -y install dnf-utils + debuginfo-install -y \ glib2 \ glibc \ libgusb \ From 3d4cf44f9b04800c8da9d7d91e4b397792637c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 23 Jun 2023 13:37:58 -0400 Subject: [PATCH 42/50] ci: Add gnome-desktop-testing to the image It can be used to run the installed tests. --- .gitlab-ci/libfprint-image-variables.yaml | 2 +- .gitlab-ci/libfprint-templates.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci/libfprint-image-variables.yaml b/.gitlab-ci/libfprint-image-variables.yaml index 5244a2bd..e568839b 100644 --- a/.gitlab-ci/libfprint-image-variables.yaml +++ b/.gitlab-ci/libfprint-image-variables.yaml @@ -1,2 +1,2 @@ variables: - LIBFPRINT_IMAGE_TAG: v2 + LIBFPRINT_IMAGE_TAG: v3 diff --git a/.gitlab-ci/libfprint-templates.yaml b/.gitlab-ci/libfprint-templates.yaml index 46e0d475..9c8de1ca 100644 --- a/.gitlab-ci/libfprint-templates.yaml +++ b/.gitlab-ci/libfprint-templates.yaml @@ -13,6 +13,7 @@ glib2-devel glibc-devel gobject-introspection-devel + gnome-desktop-testing gtk-doc gtk3-devel libabigail From ba3cc04e8474e1b43195be334182162b74284a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 23 Jun 2023 13:44:59 -0400 Subject: [PATCH 43/50] tests: Add support for installed tests They allow distrubtions to check whether libfprint continues working as expected, in different contexts. --- examples/meson.build | 5 +++ meson.build | 8 +++- meson_options.txt | 4 ++ tests/driver.test.in | 5 +++ tests/meson.build | 102 +++++++++++++++++++++++++++++++++++++++++-- tests/test.in | 6 +++ 6 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 tests/driver.test.in create mode 100644 tests/test.in diff --git a/examples/meson.build b/examples/meson.build index 06615465..f28a2d46 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -21,3 +21,8 @@ executable('cpp-test', 'cpp-test.cpp', dependencies: libfprint_dep, ) + +if installed_tests + install_subdir('prints', + install_dir: installed_tests_testdir) +endif diff --git a/meson.build b/meson.build index d667b1af..80389b75 100644 --- a/meson.build +++ b/meson.build @@ -13,6 +13,11 @@ gnome = import('gnome') libfprint_conf = configuration_data() libfprint_conf.set_quoted('LIBFPRINT_VERSION', meson.project_version()) +prefix = get_option('prefix') +libdir = prefix / get_option('libdir') +libexecdir = prefix / get_option('libexecdir') +datadir = prefix / get_option('datadir') + cc = meson.get_compiler('c') cpp = meson.get_compiler('cpp') host_system = host_machine.system() @@ -297,7 +302,6 @@ subdir('libfprint') configure_file(output: 'config.h', configuration: libfprint_conf) -subdir('examples') if get_option('doc') subdir('doc') endif @@ -308,6 +312,8 @@ endif subdir('data') subdir('tests') +subdir('examples') + pkgconfig = import('pkgconfig') pkgconfig.generate( name: versioned_libname, diff --git a/meson_options.txt b/meson_options.txt index f9b801fd..3c4cd269 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -30,3 +30,7 @@ option('doc', description: 'Whether to build the API documentation', type: 'boolean', value: true) +option('installed-tests', + description: 'Whether to install the installed tests', + type: 'boolean', + value: true) diff --git a/tests/driver.test.in b/tests/driver.test.in new file mode 100644 index 00000000..6416eea6 --- /dev/null +++ b/tests/driver.test.in @@ -0,0 +1,5 @@ +[Test] +Type=session +# We can't use TestEnvironment as per +# https://gitlab.gnome.org/GNOME/gnome-desktop-testing/-/issues/1 +Exec=env @driver_env@ @installed_tests_execdir@/@umockdev_test_name@ @installed_tests_testdir@/@driver_test@ diff --git a/tests/meson.build b/tests/meson.build index 4249ed1e..b172c1ae 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -23,6 +23,13 @@ envs.set('FP_PRINTS_PATH', meson.project_source_root() / 'examples' / 'prints') envs.set('NO_AT_BRIDGE', '1') +python3 = find_program('python3') + +installed_tests = get_option('installed-tests') +installed_tests_execdir = libexecdir / 'installed-tests' / versioned_libname +installed_tests_testdir = datadir / 'installed-tests' / versioned_libname +installed_tests_libdir = libdir + drivers_tests = [ 'aes2501', 'aes3500', @@ -54,6 +61,16 @@ if get_option('introspection') output: 'create-driver-test.py') endif +env_parser_cmd = ''' +import os; +print(" ".join([f"{k}={v}" for k, v in os.environ.items() + if k.startswith("FP_") or k.startswith("G_")])) +''' + +envs_str = run_command(python3, '-c', env_parser_cmd, + env: envs, + check: installed_tests).stdout().strip() + if get_option('introspection') envs.prepend('GI_TYPELIB_PATH', meson.project_build_root() / 'libfprint') virtual_devices_tests = [ @@ -61,9 +78,9 @@ if get_option('introspection') 'virtual-device', ] - python3 = find_program('python3') unittest_inspector = find_program('unittest_inspector.py') - umockdev_test = find_program('umockdev-test.py') + umockdev_test_name = 'umockdev-test.py' + umockdev_test = find_program(umockdev_test_name) foreach vdtest: virtual_devices_tests driver_name = '_'.join(vdtest.split('-')) @@ -95,6 +112,31 @@ if get_option('introspection') env: envs, ) endforeach + + if installed_tests + install_data(base_args, + install_dir: installed_tests_execdir, + install_mode: 'rwxr-xr-x', + ) + + configure_file( + input: 'test.in', + output: vdtest + '.test', + install_dir: installed_tests_testdir, + configuration: { + # FIXME: use fs.name() on meson 0.58 + 'exec': installed_tests_execdir / '@0@'.format(base_args[0]).split('/')[-1], + 'env': ' '.join([ + envs_str, + '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': '', + }, + ) + endif else test(vdtest, find_program('sh'), @@ -103,6 +145,7 @@ if get_option('introspection') endif endforeach + driver_tests_enabled = false foreach driver_test: drivers_tests driver_name = driver_test.split('-')[0] driver_envs = envs @@ -110,6 +153,7 @@ if get_option('introspection') if (driver_name in supported_drivers and gusb_dep.version().version_compare('>= 0.3.0')) + driver_tests_enabled = true test(driver_test, python3, args: [ @@ -121,6 +165,32 @@ if get_option('introspection') timeout: 15, depends: libfprint_typelib, ) + + if installed_tests + driver_envs_str = run_command(python3, '-c', env_parser_cmd, + env: driver_envs, + check: true).stdout().strip() + + configure_file( + input: 'driver.test.in', + output: 'driver-' + driver_test + '.test', + install_dir: installed_tests_testdir, + configuration: { + 'installed_tests_execdir': installed_tests_execdir, + 'installed_tests_testdir': installed_tests_testdir, + 'umockdev_test_name': umockdev_test_name, + 'driver_test': driver_test, + 'driver_env': ' '.join([ + driver_envs_str, + 'LD_LIBRARY_PATH=' + installed_tests_libdir, + # FIXME: Adding this requires gnome-desktop-testing!12 + # 'GI_TYPELIB_PATH=' + installed_tests_libdir / 'girepository-1.0', + ]), + }, + ) + + install_subdir(driver_test, install_dir: installed_tests_testdir) + endif else test(driver_test, find_program('sh'), @@ -128,6 +198,17 @@ if get_option('introspection') ) endif endforeach + + if installed_tests and driver_tests_enabled + install_data(umockdev_test.full_path(), + install_dir: installed_tests_execdir, + install_mode: 'rwxr-xr-x', + ) + install_data('capture.py', + install_dir: installed_tests_execdir, + install_mode: 'rwxr-xr-x', + ) + endif else warning('Skipping all driver tests as introspection bindings are missing') test('virtual-image', @@ -200,13 +281,28 @@ foreach test_name: unit_tests sources: [basename + '.c', test_config_h], dependencies: [ libfprint_private_dep ] + extra_deps, c_args: common_cflags, - link_with: test_utils, + link_whole: test_utils, + install: installed_tests, + install_dir: installed_tests_execdir, ) test(test_name, test_exe, suite: ['unit-tests'], env: envs, ) + + configure_file( + input: 'test.in', + output: test_name + '.test', + install: installed_tests, + install_dir: installed_tests_testdir, + configuration: { + 'exec': installed_tests_execdir / basename, + 'env': envs_str, + 'extra_content': 'TestEnvironment=LD_LIBRARY_PATH=' + + installed_tests_libdir, + }, + ) endforeach # Run udev rule generator with fatal warnings diff --git a/tests/test.in b/tests/test.in new file mode 100644 index 00000000..1c8eecb2 --- /dev/null +++ b/tests/test.in @@ -0,0 +1,6 @@ +[Test] +Type=session +# We can't use TestEnvironment as per +# https://gitlab.gnome.org/GNOME/gnome-desktop-testing/-/issues/1 +Exec=env @env@ @exec@ +@extra_content@ From f68e0972c28486ef6716f0bab364edbcc8191eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 28 Jun 2023 15:56:27 +0200 Subject: [PATCH 44/50] ci: Auto-Retry jobs on infrastructure failures --- .gitlab-ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7991dedb..6f8d871a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,6 +6,16 @@ include: file: '/templates/fedora.yml' - remote: 'https://gitlab.gnome.org/GNOME/citemplates/-/raw/master/flatpak/flatpak_ci_initiative.yml' +default: + # Auto-retry jobs in case of infra failures + retry: + max: 1 + when: + - 'runner_system_failure' + - 'stuck_or_timeout_failure' + - 'scheduler_failure' + - 'api_failure' + variables: extends: .libfprint_common_variables FDO_DISTRIBUTION_TAG: $LIBFPRINT_IMAGE_TAG From 5ff794c105afa7653608741933cd982d1c461fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 23 Jun 2023 13:45:14 -0400 Subject: [PATCH 45/50] ci: Run installed tests --- .gitlab-ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6f8d871a..316540bf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -118,6 +118,27 @@ test_valgrind: - _build/meson-logs/testlog-valgrind.txt expire_in: 1 week +test_installed: + stage: test + except: + variables: + - $CI_PIPELINE_SOURCE == "schedule" + script: + - meson setup _build --prefix=/usr -Ddrivers=all + - meson install -C _build + - 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/ + --parallel=0 + artifacts: + expose_as: 'GNOME Tests Runner logs' + when: always + paths: + - _build/meson-logs + - _build/installed-tests-report + expire_in: 1 week + test_scan_build: stage: test From 8562f8a9646cb1c7a9419cb9c77f17f3ba1d9e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 28 Jun 2023 16:11:12 +0200 Subject: [PATCH 46/50] ci: Use meson commands only to setup, compile and install Old ones are semi-deprecated, so let's adjust to what upstream suggests --- .gitlab-ci.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 316540bf..cb088a1f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,16 +42,16 @@ image: $FEDORA_IMAGE .build_one_driver_template: &build_one_driver script: # Build with a driver that doesn't need imaging, or nss - - meson --werror -Ddrivers=$driver . _build - - ninja -C _build + - meson setup _build --werror -Ddrivers=$driver + - meson compile -C _build - rm -rf _build/ .build_template: &build script: # And build with everything - - meson --werror -Ddrivers=all . _build - - ninja -C _build - - ninja -C _build install + - meson setup _build --werror -Ddrivers=all + - meson compile -C _build + - meson install -C _build .build_template: &check_abi script: @@ -80,10 +80,9 @@ test: variables: - $CI_PIPELINE_SOURCE == "schedule" script: - - meson --werror -Ddrivers=all -Db_coverage=true . _build - - ninja -C _build + - meson setup _build --werror -Ddrivers=all -Db_coverage=true - meson test -C _build --print-errorlogs --no-stdsplit --timeout-multiplier 3 - - ninja -C _build coverage || true # FIXME: always enable this once meson is fixed + - ninja -C _build coverage - cat _build/meson-logs/coverage.txt || true artifacts: reports: @@ -105,8 +104,8 @@ test_valgrind: variables: - $CI_PIPELINE_SOURCE == "schedule" script: - - meson -Ddrivers=all . _build - - ninja -C _build + - meson setup _build -Ddrivers=all + - meson compile -C _build - meson test -C _build --print-errorlogs --no-stdsplit --setup=valgrind artifacts: reports: @@ -147,9 +146,10 @@ test_scan_build: - $CI_PIPELINE_SOURCE == "schedule" allow_failure: true script: - - meson -Ddrivers=all . _build + - meson setup _build -Ddrivers=all # Wrapper to add --status-bugs and disable malloc checker - - SCANBUILD=$CI_PROJECT_DIR/.gitlab-ci/scan-build ninja -C _build scan-build + - SCANBUILD=$CI_PROJECT_DIR/.gitlab-ci/scan-build + ninja -C _build scan-build artifacts: paths: - _build/meson-logs From 903ee43b2d03a134c612a424b895ec98cafa7afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 28 Jun 2023 16:26:23 +0200 Subject: [PATCH 47/50] ci: Do not force-rebuild if an image with such tag exists --- .gitlab-ci.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb088a1f..fd03751e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -207,7 +207,6 @@ flatpak: - $CI_PIPELINE_SOURCE == "never" variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image - FDO_FORCE_REBUILD: 1 # a list of packages to install FDO_DISTRIBUTION_PACKAGES: $LIBFPRINT_DEPENDENCIES @@ -217,14 +216,22 @@ flatpak: FDO_DISTRIBUTION_EXEC: | $LIBFPRINT_EXEC +.container_fedora_build_forced: + variables: + FDO_FORCE_REBUILD: 1 + container_fedora_build_schedule: - extends: .container_fedora_build_base + extends: + - .container_fedora_build_base + - .container_fedora_build_forced only: variables: - $CI_PIPELINE_SOURCE == "schedule" && $CRON_TASK == "BUILD_CI_IMAGES" container_fedora_build_manual: - extends: .container_fedora_build_base + extends: + - .container_fedora_build_base + - .container_fedora_build_forced only: variables: - $LIBFPRINT_CI_ACTION == "build-image" From d37b255a11795f7e498997ed4a36a8c8bd2df408 Mon Sep 17 00:00:00 2001 From: hermanlin Date: Mon, 10 Jul 2023 11:17:27 +0800 Subject: [PATCH 48/50] elanmoc: add PID 0x0C99 --- data/autosuspend.hwdb | 1 + libfprint/drivers/elanmoc/elanmoc.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 39ace7b9..e26030fa 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -149,6 +149,7 @@ usb:v04F3p0C82* usb:v04F3p0C88* usb:v04F3p0C8C* usb:v04F3p0C8D* +usb:v04F3p0C99* ID_AUTOSUSPEND=1 ID_PERSIST=0 diff --git a/libfprint/drivers/elanmoc/elanmoc.c b/libfprint/drivers/elanmoc/elanmoc.c index 471189fd..0261bfff 100644 --- a/libfprint/drivers/elanmoc/elanmoc.c +++ b/libfprint/drivers/elanmoc/elanmoc.c @@ -31,6 +31,7 @@ static const FpIdEntry id_table[] = { { .vid = 0x04f3, .pid = 0x0c88, }, { .vid = 0x04f3, .pid = 0x0c8c, }, { .vid = 0x04f3, .pid = 0x0c8d, }, + { .vid = 0x04f3, .pid = 0x0c99, }, { .vid = 0, .pid = 0, .driver_data = 0 }, /* terminating entry */ }; @@ -1086,6 +1087,10 @@ elanmoc_open (FpDevice *device) self->max_moc_enroll_time = 11; break; + case 0x0c99: + self->max_moc_enroll_time = 14; + break; + case 0x0c8d: self->max_moc_enroll_time = 17; break; From 96645eaa7ae99a12e08b65e24a8610a3c57a92b3 Mon Sep 17 00:00:00 2001 From: swbgdx Date: Thu, 13 Jul 2023 15:04:28 +0800 Subject: [PATCH 49/50] goodixmoc: Add PID 0x633C and 0x6304 --- data/autosuspend.hwdb | 2 ++ libfprint/drivers/goodixmoc/goodix.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index e26030fa..603c5c1f 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -176,7 +176,9 @@ usb:v27C6p609C* usb:v27C6p60A2* usb:v27C6p60A4* usb:v27C6p60BC* +usb:v27C6p6304* usb:v27C6p631C* +usb:v27C6p633C* usb:v27C6p634C* usb:v27C6p6384* usb:v27C6p639C* diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index 2b71f2b9..bacb4845 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -1365,7 +1365,9 @@ gx_fp_probe (FpDevice *device) case 0x6094: case 0x609C: case 0x60BC: + case 0x6304: case 0x631C: + case 0x633C: case 0x634C: case 0x6384: case 0x639C: @@ -1611,7 +1613,9 @@ static const FpIdEntry id_table[] = { { .vid = 0x27c6, .pid = 0x60A2, }, { .vid = 0x27c6, .pid = 0x60A4, }, { .vid = 0x27c6, .pid = 0x60BC, }, + { .vid = 0x27c6, .pid = 0x6304, }, { .vid = 0x27c6, .pid = 0x631C, }, + { .vid = 0x27c6, .pid = 0x633C, }, { .vid = 0x27c6, .pid = 0x634C, }, { .vid = 0x27c6, .pid = 0x6384, }, { .vid = 0x27c6, .pid = 0x639C, }, From 135a015b6a780e85f828a1bb9a62a2ee0c72e04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 17 Aug 2023 05:08:04 +0200 Subject: [PATCH 50/50] Release 1.94.6 --- NEWS | 15 +++++++++++++++ meson.build | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 95c8b2a1..1b87665e 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. +2023-08-17: v1.94.6 release + +Highlights: + * goodixmoc: New PIDs 0x60A4, 0x60BC, 0x6092, 0x633C and 0x6304. + * goodixmoc: Fix missing "enroll create" state. + * elanmoc: New PID 0x0C99. + * upektc: Improve compatibility with sensors 147e:2016. + * aes4000: Actually support 08FF:5501 devices. + * virtual-device-listener: Fix failing tests with GLib 2.76 + * tests: Add installed tests + +Bugs fixed: + * #526 libfprint: fpcmoc: use after free if enrollment or identification is + cancelled (Vasily Khoruzhick) + 2022-10-13: v1.94.5 release Highlights: diff --git a/meson.build b/meson.build index 80389b75..0b7569bd 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfprint', [ 'c', 'cpp' ], - version: '1.94.5', + version: '1.94.6', license: 'LGPLv2.1+', default_options: [ 'buildtype=debugoptimized',