From 55cb3ec13f5841472baf588f1c31702a91790995 Mon Sep 17 00:00:00 2001 From: luoqing Date: Thu, 18 Jan 2024 14:31:30 +0800 Subject: [PATCH] Synchronize data from the master branch to openEuler-22.03-LTS-Next --- ...ix-the-issue-of-not-obtaining-featur.patch | 78 - ...river-Fix-compilation-issues-with-st.patch | 29 - ...-issue-of-duplicate-binding-of-the-s.patch | 44 - ...-issue-where-only-one-ukey-can-be-bo.patch | 1430 -------------- ...-Fixed-a-crash-caused-by-repeatedly-.patch | 30 + ...the-code-according-to-the-https-gite.patch | 1752 +++++++++++++++++ ...river-when-the-iris-face-integrated-.patch | 100 + ...ver-is-enabled-the-device-is-scanned.patch | 386 ++++ ...ix-build-fix-file-install-path-error.patch | 29 + kiran-authentication-devices-2.5.1.tar.gz | Bin 65592 -> 0 bytes kiran-authentication-devices-2.5.2.tar.gz | Bin 0 -> 68707 bytes kiran-authentication-devices.spec | 36 +- 12 files changed, 2322 insertions(+), 1592 deletions(-) delete mode 100644 0001-fix-feature-db-Fix-the-issue-of-not-obtaining-featur.patch delete mode 100644 0001-fix-mf-iristar-driver-Fix-compilation-issues-with-st.patch delete mode 100644 0001-fix-ukey-Fix-the-issue-of-duplicate-binding-of-the-s.patch delete mode 100644 0001-fix-ukey-Fix-the-issue-where-only-one-ukey-can-be-bo.patch create mode 100644 0002-fix-ukey-manager-Fixed-a-crash-caused-by-repeatedly-.patch create mode 100644 0003-refactor-Modify-the-code-according-to-the-https-gite.patch create mode 100644 0004-fix-mf-iristar-driver-when-the-iris-face-integrated-.patch create mode 100644 0005-fix-When-the-driver-is-enabled-the-device-is-scanned.patch create mode 100644 0006-fix-build-fix-file-install-path-error.patch delete mode 100644 kiran-authentication-devices-2.5.1.tar.gz create mode 100644 kiran-authentication-devices-2.5.2.tar.gz diff --git a/0001-fix-feature-db-Fix-the-issue-of-not-obtaining-featur.patch b/0001-fix-feature-db-Fix-the-issue-of-not-obtaining-featur.patch deleted file mode 100644 index d0b704d..0000000 --- a/0001-fix-feature-db-Fix-the-issue-of-not-obtaining-featur.patch +++ /dev/null @@ -1,78 +0,0 @@ -From bdb3eb90ad2ab0f08365b71fefb33c76b96c4359 Mon Sep 17 00:00:00 2001 -From: luoqing -Date: Fri, 2 Jun 2023 19:09:11 +0800 -Subject: [PATCH] fix(feature-db):Fix the issue of not obtaining features from - the database when deviceSerialNumber is empty -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -- 修复deviceSerialNumber为空时,从数据库获取不到feature的问题 ---- - src/device/fingerprint/fp-zk-device.cpp | 1 + - src/feature-db.cpp | 18 +++++++++++++++--- - 2 files changed, 16 insertions(+), 3 deletions(-) - -diff --git a/src/device/fingerprint/fp-zk-device.cpp b/src/device/fingerprint/fp-zk-device.cpp -index 4d8abc0..16ceb37 100644 ---- a/src/device/fingerprint/fp-zk-device.cpp -+++ b/src/device/fingerprint/fp-zk-device.cpp -@@ -398,6 +398,7 @@ QString FPZKDevice::identifyFeature(QByteArray fpTemplate, QStringList featureID - - if (saveList.count() == 0) - { -+ KLOG_DEBUG() << "no found feature"; - return QString(); - } - -diff --git a/src/feature-db.cpp b/src/feature-db.cpp -index ee0a4bd..a8aa883 100644 ---- a/src/feature-db.cpp -+++ b/src/feature-db.cpp -@@ -92,7 +92,7 @@ bool FeatureDB::addFeature(const QString &featureID, QByteArray feature, DeviceI - query.bindValue(":idVendor", deviceInfo.idVendor); - query.bindValue(":idProduct", deviceInfo.idProduct); - query.bindValue(":deviceType", (int)deviceType); -- query.bindValue(":deviceSerialNumber", deviceSerialNumber); -+ query.bindValue(":deviceSerialNumber", deviceSerialNumber.isEmpty() ? QVariant(QVariant::String) : deviceSerialNumber); - return query.exec(); - } - -@@ -121,12 +121,19 @@ QByteArray FeatureDB::getFeature(const QString &featureID) - QList FeatureDB::getFeatures(const QString &idVendor, const QString &idProduct, DeviceType deviceType, const QString &deviceSerialNumber) - { - QSqlQuery query(m_database); -- query.prepare("SELECT feature FROM feature WHERE idVendor = :Vid AND idProduct = :Pid AND deviceType = :devType AND deviceSerialNumber = :serialNumber"); -+ QString sql = "SELECT feature FROM feature WHERE idVendor = :Vid AND idProduct = :Pid AND deviceType = :devType"; -+ if (!deviceSerialNumber.isEmpty()) -+ { -+ sql.append(" AND deviceSerialNumber = :serialNumber"); -+ } -+ -+ query.prepare(sql); - query.bindValue(":Vid", idVendor); - query.bindValue(":Pid", idProduct); - query.bindValue(":devType", (int)deviceType); - query.bindValue(":serialNumber", deviceSerialNumber); - query.exec(); -+ - QByteArrayList featuresList; - while (query.next()) - { -@@ -153,7 +160,12 @@ QList FeatureDB::getAllFeatures() - QStringList FeatureDB::getFeatureIDs(const QString &idVendor, const QString &idProduct, DeviceType deviceType, const QString &deviceSerialNumber) - { - QSqlQuery query(m_database); -- query.prepare("SELECT featureID FROM feature WHERE idVendor = :Vid AND idProduct = :Pid AND deviceType = :devType AND deviceSerialNumber = :serialNumber"); -+ QString sql = "SELECT featureID FROM feature WHERE idVendor = :Vid AND idProduct = :Pid AND deviceType = :devType"; -+ if (!deviceSerialNumber.isEmpty()) -+ { -+ sql.append(" AND deviceSerialNumber = :serialNumber"); -+ } -+ - query.bindValue(":Vid", idVendor); - query.bindValue(":Pid", idProduct); - query.bindValue(":devType", (int)deviceType); --- -2.33.0 - diff --git a/0001-fix-mf-iristar-driver-Fix-compilation-issues-with-st.patch b/0001-fix-mf-iristar-driver-Fix-compilation-issues-with-st.patch deleted file mode 100644 index 9bb88c8..0000000 --- a/0001-fix-mf-iristar-driver-Fix-compilation-issues-with-st.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 3fe0189b4bb5cd0d8fb3698dcdf136d5f1a64e8e Mon Sep 17 00:00:00 2001 -From: luoqing -Date: Wed, 24 May 2023 17:40:53 +0800 -Subject: [PATCH] fix(mf-iristar-driver):Fix compilation issues with std:: - function in lower versions -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -- 修复std::function在低版本的编译问题 ---- - src/driver/multi-function/mf-iristar-driver.cpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/driver/multi-function/mf-iristar-driver.cpp b/src/driver/multi-function/mf-iristar-driver.cpp -index 4af8cff..298a0e8 100644 ---- a/src/driver/multi-function/mf-iristar-driver.cpp -+++ b/src/driver/multi-function/mf-iristar-driver.cpp -@@ -12,6 +12,7 @@ - * Author: luoqing - */ - -+#include - #include "mf-iristar-driver.h" - #include - #include --- -2.33.0 - diff --git a/0001-fix-ukey-Fix-the-issue-of-duplicate-binding-of-the-s.patch b/0001-fix-ukey-Fix-the-issue-of-duplicate-binding-of-the-s.patch deleted file mode 100644 index 6a400d3..0000000 --- a/0001-fix-ukey-Fix-the-issue-of-duplicate-binding-of-the-s.patch +++ /dev/null @@ -1,44 +0,0 @@ -From aab75172b1f174daf68c401b6e350f994f14fad1 Mon Sep 17 00:00:00 2001 -From: luoqing -Date: Mon, 12 Jun 2023 15:23:30 +0800 -Subject: [PATCH] fix(ukey):Fix the issue of duplicate binding of the same UKey - device -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -- 修复同一个UKey设备可以重复绑定的问题 - -Related #I78P3F ---- - src/device/ukey/ukey-ft-device.cpp | 1 + - src/feature-db.cpp | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/src/device/ukey/ukey-ft-device.cpp b/src/device/ukey/ukey-ft-device.cpp -index e8f5070..14f09ef 100644 ---- a/src/device/ukey/ukey-ft-device.cpp -+++ b/src/device/ukey/ukey-ft-device.cpp -@@ -210,6 +210,7 @@ ULONG UKeyFTDevice::createContainer(const QString &pin, DEVHANDLE devHandle, HAP - bool UKeyFTDevice::isExistBinding() - { - QStringList featureIDs = FeatureDB::getInstance()->getFeatureIDs(deviceInfo().idVendor, deviceInfo().idProduct, deviceType(), deviceSerialNumber()); -+ KLOG_DEBUG() << "Existing Binding featureIDs:" << featureIDs; - for (auto id : featureIDs) - { - FeatureInfo info = FeatureDB::getInstance()->getFeatureInfo(id); -diff --git a/src/feature-db.cpp b/src/feature-db.cpp -index a8aa883..6cf8735 100644 ---- a/src/feature-db.cpp -+++ b/src/feature-db.cpp -@@ -166,6 +166,7 @@ QStringList FeatureDB::getFeatureIDs(const QString &idVendor, const QString &idP - sql.append(" AND deviceSerialNumber = :serialNumber"); - } - -+ query.prepare(sql); - query.bindValue(":Vid", idVendor); - query.bindValue(":Pid", idProduct); - query.bindValue(":devType", (int)deviceType); --- -2.33.0 - diff --git a/0001-fix-ukey-Fix-the-issue-where-only-one-ukey-can-be-bo.patch b/0001-fix-ukey-Fix-the-issue-where-only-one-ukey-can-be-bo.patch deleted file mode 100644 index bcc3cad..0000000 --- a/0001-fix-ukey-Fix-the-issue-where-only-one-ukey-can-be-bo.patch +++ /dev/null @@ -1,1430 +0,0 @@ -From 80f7c120db60ba057a6d3ba673fc3daafe3f2a8b Mon Sep 17 00:00:00 2001 -From: luoqing -Date: Fri, 26 May 2023 16:01:55 +0800 -Subject: [PATCH] fix(ukey):Fix the issue where only one ukey can be bound to a - device;Fix some self-test bugs -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -- 修复一台设备只能绑一个ukey的问题 - 修复一些自测缺陷 - - Closes #I78P3F ---- - CMakeLists.txt | 18 +- - data/com.kylinsec.Kiran.AuthDevice.Device.xml | 4 +- - src/auth-device-manager.cpp | 116 ++++--- - src/context/context.h | 4 - - src/context/finger-vein/fv-sd-context.cpp | 1 - - src/context/fingerprint/fp-zk-context.cpp | 1 - - src/context/multi-function-context.cpp | 2 - - src/context/ukey/ukey-ft-context.cpp | 1 - - src/device/auth-device.cpp | 2 +- - src/device/auth-device.h | 3 + - src/device/bio-device.cpp | 2 +- - src/device/finger-vein/fv-sd-device.cpp | 2 +- - src/device/fingerprint/fp-zk-device.cpp | 2 +- - src/device/ukey/ukey-ft-device.cpp | 321 ++++++++++-------- - src/device/ukey/ukey-ft-device.h | 30 +- - .../multi-function/mf-iristar-driver.cpp | 2 +- - src/driver/ukey/ukey-skf-driver.cpp | 149 ++++++-- - src/driver/ukey/ukey-skf-driver.h | 10 +- - src/feature-db.cpp | 38 ++- - src/feature-db.h | 24 +- - ukey-manager/ukey-manager.cpp | 26 +- - 21 files changed, 460 insertions(+), 298 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index e198c84..122d4be 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -1,11 +1,3 @@ --set(CMAKE_INCLUDE_CURRENT_DIR ON) -- --set(CMAKE_AUTOUIC ON) --set(CMAKE_AUTOMOC ON) --set(CMAKE_AUTORCC ON) -- --set(CMAKE_CXX_STANDARD 11) --set(CMAKE_CXX_STANDARD_REQUIRED ON) - - cmake_minimum_required(VERSION 3.2) - -@@ -17,10 +9,18 @@ find_package(PkgConfig REQUIRED) - find_package(Qt5 REQUIRED COMPONENTS Core DBus Sql Concurrent LinguistTools) - pkg_search_module(KLOG_QT5 REQUIRED klog-qt5) - -+set(CMAKE_INCLUDE_CURRENT_DIR ON) -+ -+set(CMAKE_AUTOUIC ON) -+set(CMAKE_AUTOMOC ON) -+set(CMAKE_AUTORCC ON) -+ -+set(CMAKE_CXX_STANDARD 11) -+set(CMAKE_CXX_STANDARD_REQUIRED ON) -+ - set(TRANSLATION_INSTALL_DIR ${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME}/translations) - configure_file(${CMAKE_SOURCE_DIR}/data/config.h.in ${CMAKE_BINARY_DIR}/config.h) - -- - add_subdirectory(src) - add_subdirectory(data) - add_subdirectory(ukey-manager) -diff --git a/data/com.kylinsec.Kiran.AuthDevice.Device.xml b/data/com.kylinsec.Kiran.AuthDevice.Device.xml -index ad41a33..9b22600 100644 ---- a/data/com.kylinsec.Kiran.AuthDevice.Device.xml -+++ b/data/com.kylinsec.Kiran.AuthDevice.Device.xml -@@ -55,7 +55,7 @@ - A number between 0 and 100 to describe the progress of enrolling fingerprint. 0 is failed - - -- Represent the status of the enrollment. Refer to enum DeviceType in file kiran-auth-device-i.h -+ Represent the status of the enrollment. Refer to enum EnrollStatus in file kiran-auth-device-i.h - - - Status description information. -@@ -67,7 +67,7 @@ - Feature ID. - - -- Represent the status of the identification. Refer to enum DeviceType in file kiran-auth-device-i.h. -+ Represent the status of the identification. Refer to enum IdentifyStatus in file kiran-auth-device-i.h. - - - Status description information. -diff --git a/src/auth-device-manager.cpp b/src/auth-device-manager.cpp -index ab9a0df..5c7577b 100644 ---- a/src/auth-device-manager.cpp -+++ b/src/auth-device-manager.cpp -@@ -25,6 +25,7 @@ - #include "kiran-auth-device-i.h" - #include "polkit-proxy.h" - #include "utils.h" -+#include "device/ukey/ukey-ft-device.h" - - namespace Kiran - { -@@ -147,11 +148,30 @@ QString AuthDeviceManager::GetDriversByType(int device_type) - - void AuthDeviceManager::onRemove(const QDBusMessage& message, const QString& feature_id) - { -+ FeatureInfo featureInfo = FeatureDB::getInstance()->getFeatureInfo(feature_id); - bool result = FeatureDB::getInstance()->deleteFeature(feature_id); - KLOG_DEBUG() << "deleteFeature:" << feature_id - << "exec:" << result; - auto replyMessage = message.createReply(); - QDBusConnection::systemBus().send(replyMessage); -+ -+ if (featureInfo.deviceType == DEVICE_TYPE_UKey) -+ { -+ AuthDeviceList deviceList = m_deviceMap.values(); -+ for (auto device : deviceList) -+ { -+ if (device->deviceType() != DEVICE_TYPE_UKey) -+ { -+ continue; -+ } -+ auto ukeyDevice = qobject_cast(device); -+ if (ukeyDevice->deviceSerialNumber() != featureInfo.deviceSerialNumber) -+ { -+ continue; -+ } -+ ukeyDevice->resetUkey(); -+ } -+ } - } - - // TODO:是否需要监听配置文件的改变 -@@ -297,27 +317,30 @@ void AuthDeviceManager::handleDeviceDeleted() - int deviceType; - Q_FOREACH (auto busPath, oldBusList) - { -- if (!newBusList.contains(busPath)) -+ if (newBusList.contains(busPath)) - { -- AuthDevicePtr oldAuthDevice = m_deviceMap.value(busPath); -- deviceID = oldAuthDevice->deviceID(); -- deviceType = oldAuthDevice->deviceType(); -- m_deviceMap.remove(busPath); -+ continue; -+ } -+ -+ AuthDevicePtr oldAuthDevice = m_deviceMap.value(busPath); -+ deviceID = oldAuthDevice->deviceID(); -+ deviceType = oldAuthDevice->deviceType(); -+ int removeCount = m_deviceMap.remove(busPath); -+ oldAuthDevice.clear(); -+ Q_EMIT m_dbusAdaptor->DeviceDeleted(deviceType, deviceID); - -- QMapIterator i(m_retreyCreateDeviceMap); -- while (i.hasNext()) -+ QMapIterator i(m_retreyCreateDeviceMap); -+ while (i.hasNext()) -+ { -+ i.next(); -+ if (i.key().busPath == busPath) - { -- i.next(); -- if (i.key().busPath == busPath) -- { -- m_retreyCreateDeviceMap.remove(i.key()); -- } -+ m_retreyCreateDeviceMap.remove(i.key()); - } -- KLOG_DEBUG() << "device delete: " << busPath; -- break; - } -+ KLOG_DEBUG() << "device delete: " << busPath; -+ break; - } -- Q_EMIT m_dbusAdaptor->DeviceDeleted(deviceType, deviceID); - } - - void AuthDeviceManager::handleDeviceReCreate() -@@ -325,43 +348,40 @@ void AuthDeviceManager::handleDeviceReCreate() - if (m_retreyCreateDeviceMap.count() == 0) - { - m_timer.stop(); -+ return; - } -- else -+ -+ QMapIterator i(m_retreyCreateDeviceMap); -+ while (i.hasNext()) - { -- QMapIterator i(m_retreyCreateDeviceMap); -- while (i.hasNext()) -+ i.next(); -+ if (i.value() >= 2) - { -- i.next(); -- if (i.value() >= 2) -- { -- m_retreyCreateDeviceMap.remove(i.key()); -- } -- else -- { -- auto deviceInfo = i.key(); -- AuthDeviceList deviceList = m_contextFactory->createDevices(deviceInfo.idVendor, deviceInfo.idProduct); -- if (deviceList.count() != 0) -- { -- Q_FOREACH (auto device, deviceList) -- { -- m_deviceMap.insert(deviceInfo.busPath, device); -- Q_EMIT this->DeviceAdded(device->deviceType(), device->deviceID()); -- Q_EMIT m_dbusAdaptor->DeviceAdded(device->deviceType(), device->deviceID()); -- -- KLOG_DEBUG() << "device added" -- << "idVendor:" << deviceInfo.idVendor -- << "idProduct:" << deviceInfo.idProduct -- << "bus:" << deviceInfo.busPath; -- } -- -- m_retreyCreateDeviceMap.remove(i.key()); -- } -- else -- { -- m_retreyCreateDeviceMap.insert(i.key(), i.value() + 1); -- } -- } -+ m_retreyCreateDeviceMap.remove(i.key()); -+ continue; -+ } -+ -+ auto deviceInfo = i.key(); -+ AuthDeviceList deviceList = m_contextFactory->createDevices(deviceInfo.idVendor, deviceInfo.idProduct); -+ if (deviceList.count() == 0) -+ { -+ m_retreyCreateDeviceMap.insert(i.key(), i.value() + 1); -+ continue; - } -+ -+ Q_FOREACH (auto device, deviceList) -+ { -+ m_deviceMap.insert(deviceInfo.busPath, device); -+ Q_EMIT this->DeviceAdded(device->deviceType(), device->deviceID()); -+ Q_EMIT m_dbusAdaptor->DeviceAdded(device->deviceType(), device->deviceID()); -+ -+ KLOG_DEBUG() << "device added" -+ << "idVendor:" << deviceInfo.idVendor -+ << "idProduct:" << deviceInfo.idProduct -+ << "bus:" << deviceInfo.busPath; -+ } -+ -+ m_retreyCreateDeviceMap.remove(i.key()); - } - } - } // namespace Kiran -diff --git a/src/context/context.h b/src/context/context.h -index 2043f82..fd6a3ff 100644 ---- a/src/context/context.h -+++ b/src/context/context.h -@@ -29,11 +29,7 @@ class Context : public QObject - public: - explicit Context(QObject *parent = nullptr); - virtual AuthDevicePtr createDevice(const QString &idVendor, const QString &idProduct) = 0; -- virtual AuthDeviceList getDevices() { return m_deviceMap.values(); }; - --protected: -- QMap m_deviceMap; -- AuthDevicePtr m_device; - }; - - } // namespace Kiran -diff --git a/src/context/finger-vein/fv-sd-context.cpp b/src/context/finger-vein/fv-sd-context.cpp -index c417745..6265ba4 100644 ---- a/src/context/finger-vein/fv-sd-context.cpp -+++ b/src/context/finger-vein/fv-sd-context.cpp -@@ -59,7 +59,6 @@ AuthDevicePtr FVSDContext::createDevice(const QString& idVendor, const QString& - } - sdDevice->setDeviceName(deviceName); - sdDevice->setDeviceInfo(idVendor, idProduct); -- m_deviceMap.insert(sdDevice->deviceID(), sdDevice); - return sdDevice; - } - } // namespace Kiran -diff --git a/src/context/fingerprint/fp-zk-context.cpp b/src/context/fingerprint/fp-zk-context.cpp -index bde2b02..82bc74a 100644 ---- a/src/context/fingerprint/fp-zk-context.cpp -+++ b/src/context/fingerprint/fp-zk-context.cpp -@@ -63,7 +63,6 @@ AuthDevicePtr FPZKContext::createDevice(const QString& idVendor, const QString& - } - zkDevice->setDeviceName(deviceName); - zkDevice->setDeviceInfo(idVendor, idProduct); -- m_deviceMap.insert(zkDevice->deviceID(), zkDevice); - return zkDevice; - } - } // namespace Kiran -diff --git a/src/context/multi-function-context.cpp b/src/context/multi-function-context.cpp -index 750689a..f3b7670 100644 ---- a/src/context/multi-function-context.cpp -+++ b/src/context/multi-function-context.cpp -@@ -81,8 +81,6 @@ AuthDevicePtr MultiFunctionContext::createIriStarDevice(const QString& idVendor, - iriStarDevice->setDeviceName(deviceName); - iriStarDevice->setDeviceInfo(idVendor, idProduct); - -- m_deviceMap.insert(iriStarDevice->deviceID(), iriStarDevice); -- - return iriStarDevice; - } - -diff --git a/src/context/ukey/ukey-ft-context.cpp b/src/context/ukey/ukey-ft-context.cpp -index e363fc1..3b1b859 100644 ---- a/src/context/ukey/ukey-ft-context.cpp -+++ b/src/context/ukey/ukey-ft-context.cpp -@@ -58,7 +58,6 @@ AuthDevicePtr UKeyFTContext::createDevice(const QString& idVendor, const QString - } - ftDevice->setDeviceName(deviceName); - ftDevice->setDeviceInfo(idVendor, idProduct); -- m_deviceMap.insert(ftDevice->deviceID(), ftDevice); - return ftDevice; - } - } // namespace Kiran -diff --git a/src/device/auth-device.cpp b/src/device/auth-device.cpp -index 0595fb1..a321723 100644 ---- a/src/device/auth-device.cpp -+++ b/src/device/auth-device.cpp -@@ -194,7 +194,7 @@ CHECK_AUTH(AuthDevice, IdentifyStop, onIdentifyStop, AUTH_USER_ADMIN) - - QStringList AuthDevice::GetFeatureIDList() - { -- QStringList featureIDs = FeatureDB::getInstance()->getFeatureIDs(m_idVendor, m_idProduct,deviceType()); -+ QStringList featureIDs = FeatureDB::getInstance()->getFeatureIDs(m_idVendor, m_idProduct,deviceType(),deviceSerialNumber()); - return featureIDs; - } - -diff --git a/src/device/auth-device.h b/src/device/auth-device.h -index b943189..8b522d0 100644 ---- a/src/device/auth-device.h -+++ b/src/device/auth-device.h -@@ -52,12 +52,14 @@ public: - DeviceStatus deviceStatus() { return m_deviceStatus; }; - QString deviceName() { return m_deviceName; }; - DeviceInfo deviceInfo(); -+ QString deviceSerialNumber() { return m_serialNumber; }; - - void setDeviceType(DeviceType deviceType) { m_deviceType = deviceType; }; - void setDeviceStatus(DeviceStatus deviceStatus) { m_deviceStatus = deviceStatus; }; - void setDeviceName(const QString &deviceName) { m_deviceName = deviceName; }; - void setDeviceInfo(const QString &idVendor, const QString &idProduct); - void setDeviceDriver(const QString &deviceDriver); -+ void setDeviceSerialNumber(const QString &serialNumber) {m_serialNumber = serialNumber;}; - - public Q_SLOTS: - virtual void EnrollStart(const QString &extraInfo); -@@ -104,6 +106,7 @@ private: - QString m_deviceName; - QString m_idVendor; - QString m_idProduct; -+ QString m_serialNumber; - QDBusObjectPath m_objectPath; - QSharedPointer m_serviceWatcher; - -diff --git a/src/device/bio-device.cpp b/src/device/bio-device.cpp -index 0b06bae..dd14f0e 100644 ---- a/src/device/bio-device.cpp -+++ b/src/device/bio-device.cpp -@@ -36,7 +36,7 @@ void BioDevice::doingEnrollStart(const QString &extraInfo) - { - KLOG_DEBUG() << "biological information enroll start"; - // 获取当前保存的特征模板,判断是否达到最大数目 -- QByteArrayList saveList = FeatureDB::getInstance()->getFeatures(deviceInfo().idVendor, deviceInfo().idProduct, deviceType()); -+ QByteArrayList saveList = FeatureDB::getInstance()->getFeatures(deviceInfo().idVendor, deviceInfo().idProduct, deviceType(),deviceSerialNumber()); - if (saveList.count() == TEMPLATE_MAX_NUMBER) - { - QString message = tr("feature has reached the upper limit of %1").arg(TEMPLATE_MAX_NUMBER); -diff --git a/src/device/finger-vein/fv-sd-device.cpp b/src/device/finger-vein/fv-sd-device.cpp -index 55e49c0..6581cf9 100644 ---- a/src/device/finger-vein/fv-sd-device.cpp -+++ b/src/device/finger-vein/fv-sd-device.cpp -@@ -399,7 +399,7 @@ QString FVSDDevice::identifyFeature(QByteArray feature, QStringList featureIDs) - DeviceInfo deviceInfo = this->deviceInfo(); - if (featureIDs.isEmpty()) - { -- saveList = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct, deviceType()); -+ saveList = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct, deviceType(),deviceSerialNumber()); - } - else - { -diff --git a/src/device/fingerprint/fp-zk-device.cpp b/src/device/fingerprint/fp-zk-device.cpp -index 92ff10d..4d8abc0 100644 ---- a/src/device/fingerprint/fp-zk-device.cpp -+++ b/src/device/fingerprint/fp-zk-device.cpp -@@ -384,7 +384,7 @@ QString FPZKDevice::identifyFeature(QByteArray fpTemplate, QStringList featureID - DeviceInfo info = this->deviceInfo(); - if (featureIDs.isEmpty()) - { -- saveList = FeatureDB::getInstance()->getFeatures(info.idVendor, info.idProduct,deviceType()); -+ saveList = FeatureDB::getInstance()->getFeatures(info.idVendor, info.idProduct,deviceType(),deviceSerialNumber()); - } - else - { -diff --git a/src/device/ukey/ukey-ft-device.cpp b/src/device/ukey/ukey-ft-device.cpp -index a6e1dfc..e8f5070 100644 ---- a/src/device/ukey/ukey-ft-device.cpp -+++ b/src/device/ukey/ukey-ft-device.cpp -@@ -23,28 +23,61 @@ - - namespace Kiran - { --UKeyFTDevice::UKeyFTDevice(QObject *parent) : AuthDevice{parent}, -- m_appHandle(nullptr), -- m_devHandle(nullptr), -- m_containerHandle(nullptr) -+QStringList UKeyFTDevice::m_existingSerialNumber; -+ -+UKeyFTDevice::UKeyFTDevice(QObject *parent) : AuthDevice{parent} - { - setDeviceType(DEVICE_TYPE_UKey); - setDeviceDriver(FT_UKEY_DRIVER_LIB); -- m_driver = QSharedPointer(new UKeySKFDriver()); -+ /** -+ * NOTE: -+ * UKey设备插入时,设备可能处在未准备好的状态,无法获取到serialNumber -+ * 如果初始化时,未获取到serialNumber,则开启定时器再次获取 -+ */ -+ if (!initSerialNumber()) -+ { -+ m_reInitSerialNumberTimer.start(1000); -+ } -+ connect(&m_reInitSerialNumberTimer, &QTimer::timeout, this, &UKeyFTDevice::initSerialNumber); - } - - UKeyFTDevice::~UKeyFTDevice() - { -+ int index = m_existingSerialNumber.indexOf(deviceSerialNumber()); -+ m_existingSerialNumber.removeAt(index); -+ KLOG_DEBUG() << "destory device, serialNumber:" << deviceSerialNumber(); - } - - bool UKeyFTDevice::initDriver() - { -- if (!m_driver->loadLibrary(FT_UKEY_DRIVER_LIB)) -+ return true; -+} -+ -+bool UKeyFTDevice::initSerialNumber() -+{ -+ UKeySKFDriver driver; -+ driver.loadLibrary(FT_UKEY_DRIVER_LIB); -+ QStringList serialNumberList = driver.enumDevSerialNumber(); -+ for (auto serialNumber : serialNumberList) -+ { -+ if (m_existingSerialNumber.contains(serialNumber)) -+ { -+ continue; -+ } -+ setDeviceSerialNumber(serialNumber); -+ m_existingSerialNumber << serialNumber; -+ break; -+ } -+ KLOG_DEBUG() << "init serial number:" << deviceSerialNumber(); -+ if (deviceSerialNumber().isEmpty()) - { - return false; - } -- -- return true; -+ else -+ { -+ m_reInitSerialNumberTimer.stop(); -+ return true; -+ } - } - - void UKeyFTDevice::doingEnrollStart(const QString &extraInfo) -@@ -52,66 +85,71 @@ void UKeyFTDevice::doingEnrollStart(const QString &extraInfo) - KLOG_DEBUG() << "ukey enroll start"; - QJsonValue ukeyValue = Utils::getValueFromJsonString(extraInfo, AUTH_DEVICE_JSON_KEY_UKEY); - auto jsonObject = ukeyValue.toObject(); -- m_pin = jsonObject.value(AUTH_DEVICE_JSON_KEY_PIN).toString(); -- bool rebinding = jsonObject.value(AUTH_DEVICE_JSON_KEY_REBINDING).toBool(); -- if (m_pin.isEmpty()) -+ QString pin = jsonObject.value(AUTH_DEVICE_JSON_KEY_PIN).toString(); -+ HANDLE devHandle = nullptr; -+ -+ KLOG_DEBUG() << "device serial number:" << deviceSerialNumber(); -+ if (pin.isEmpty()) - { - QString message = tr("The pin code cannot be empty!"); - Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_STATUS_FAIL, message); - KLOG_ERROR() << "The pin code cannot be empty!"; -- internalStopEnroll(); -- return; -+ goto end; - } - -- m_devHandle = m_driver->connectDev(); -- if (!m_devHandle) -+ if (isExistBinding()) - { -- KLOG_ERROR() << "Connect Dev failed"; -- notifyUKeyEnrollProcess(ENROLL_PROCESS_FAIL); -- internalStopEnroll(); -- return; -+ notifyUKeyEnrollProcess(ENROLL_PROCESS_REPEATED_ENROLL); -+ goto end; - } - -- if (rebinding) -+ m_driver = new UKeySKFDriver(); -+ if (!m_driver->loadLibrary(FT_UKEY_DRIVER_LIB)) - { -- ULONG ulReval = m_driver->devAuth(m_devHandle); -- if (ulReval == SAR_OK) -- { -- m_driver->deleteAllApplication(m_devHandle); -- DeviceInfo deviceInfo = this->deviceInfo(); -- QStringList idList = FeatureDB::getInstance()->getFeatureIDs(deviceInfo.idVendor, deviceInfo.idProduct, deviceType()); -- Q_FOREACH (auto id, idList) -- { -- FeatureDB::getInstance()->deleteFeature(id); -- } -- bindingUKey(); -- } -- else -- { -- KLOG_ERROR() << "rebinding failed"; -- } -+ KLOG_ERROR() << "load library failed"; -+ notifyUKeyEnrollProcess(ENROLL_PROCESS_FAIL); -+ goto end; - } -- else -+ -+ devHandle = m_driver->connectDev(deviceSerialNumber()); -+ KLOG_DEBUG() << "devHandle:" << devHandle; -+ if (!devHandle) - { -- bindingUKey(); -+ KLOG_ERROR() << "Connect Dev failed"; -+ notifyUKeyEnrollProcess(ENROLL_PROCESS_FAIL); -+ goto end; - } -+ bindingUKey(devHandle,pin); -+ m_driver->disConnectDev(devHandle); -+ -+end: - internalStopEnroll(); -+ return; - } - --void UKeyFTDevice::bindingUKey() -+void UKeyFTDevice::bindingUKey(DEVHANDLE devHandle, const QString &pin) - { -- if (isExistPublicKey()) -+ HCONTAINER containerHandle; -+ HAPPLICATION appHandle; -+ ULONG ret = createContainer(pin, devHandle, &appHandle, &containerHandle); -+ if (ret != SAR_OK) - { -- notifyUKeyEnrollProcess(ENROLL_PROCESS_REPEATED_ENROLL); -+ KLOG_ERROR() << "create container failed:" << m_driver->getErrorReason(ret); -+ notifyUKeyEnrollProcess(ENROLL_PROCESS_FAIL, ret); -+ m_driver->closeContainer(containerHandle); -+ m_driver->closeApplication(appHandle); - return; - } -- ECCPUBLICKEYBLOB publicKey = {0}; -- ULONG ret = genKeyPair(&publicKey); -+ KLOG_DEBUG() << "create container success"; - -+ ECCPUBLICKEYBLOB publicKey = {0}; -+ ret = m_driver->genECCKeyPair(containerHandle, &publicKey); - if (ret != SAR_OK) - { - KLOG_ERROR() << "gen ecc key pair failed:" << m_driver->getErrorReason(ret); - notifyUKeyEnrollProcess(ENROLL_PROCESS_FAIL, ret); -+ m_driver->closeContainer(containerHandle); -+ m_driver->closeApplication(appHandle); - return; - } - KLOG_DEBUG() << "gen ecc key pair success"; -@@ -131,87 +169,62 @@ void UKeyFTDevice::bindingUKey() - QString featureID = QCryptographicHash::hash(keyFeature, QCryptographicHash::Md5).toHex(); - DeviceInfo deviceInfo = this->deviceInfo(); - -- if (FeatureDB::getInstance()->addFeature(featureID, keyFeature, deviceInfo, deviceType())) -+ if (FeatureDB::getInstance()->addFeature(featureID, keyFeature, deviceInfo, deviceType(), deviceSerialNumber())) - { - notifyUKeyEnrollProcess(ENROLL_PROCESS_SUCCESS, SAR_OK, featureID); - } - else - { -- KLOG_DEBUG() << "save feature fail"; -+ KLOG_ERROR() << "save feature fail"; - notifyUKeyEnrollProcess(ENROLL_PROCESS_FAIL); - } --} - --bool UKeyFTDevice::isExistPublicKey() --{ -- DeviceInfo deviceInfo = this->deviceInfo(); -- auto features = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct, deviceType()); -- if (features.count() != 0) -- { -- return true; -- } -- else -- { -- return false; -- } -+ m_driver->closeContainer(containerHandle); -+ m_driver->closeApplication(appHandle); - } - --ULONG UKeyFTDevice::genKeyPair(ECCPUBLICKEYBLOB *publicKey) -+ULONG UKeyFTDevice::createContainer(const QString &pin, DEVHANDLE devHandle, HAPPLICATION *appHandle, HCONTAINER *containerHandle) - { -- ULONG ulReval; -- if (!isExistsApplication(UKEY_APP_NAME)) -- { -- // NOTE:必须通过设备认证后才能在设备内创建和删除应用 -- ulReval = m_driver->devAuth(m_devHandle); -- if (ulReval != SAR_OK) -- { -- KLOG_ERROR() << "Device auth failure: " << m_driver->getErrorReason(ulReval); -- return ulReval; -- } -- else -- { -- KLOG_DEBUG() << "device auth success"; -- } -- m_driver->deleteAllApplication(m_devHandle); -- ulReval = m_driver->createApplication(m_devHandle, m_pin, UKEY_APP_NAME, &m_appHandle); -- if (ulReval != SAR_OK) -- { -- KLOG_ERROR() << "create application failed:" << m_driver->getErrorReason(ulReval); -- return ulReval; -- } -- KLOG_DEBUG() << "create application suceess"; -- ulReval = m_driver->createContainer(m_appHandle, m_pin, UKEY_CONTAINER_NAME, &m_retryCount, &m_containerHandle); -- if (ulReval != SAR_OK) -- { -- KLOG_ERROR() << "create container failed:" << m_driver->getErrorReason(ulReval); -- return ulReval; -- } -- KLOG_DEBUG() << "create new container success"; -- } -- ulReval = m_driver->onOpenApplication(m_devHandle, (LPSTR)UKEY_APP_NAME, &m_appHandle); -+ // NOTE:必须通过设备认证后才能在设备内创建和删除应用 -+ ULONG ulReval = m_driver->devAuth(devHandle); - if (ulReval != SAR_OK) - { -- KLOG_DEBUG() << "open Application failed:" << m_driver->getErrorReason(ulReval); -+ KLOG_ERROR() << "Device auth failure: " << m_driver->getErrorReason(ulReval); - return ulReval; - } -- KLOG_DEBUG() << "open Application success"; -+ KLOG_DEBUG() << "device auth success"; -+ m_driver->deleteAllApplication(devHandle); - -- ulReval = m_driver->onOpenContainer(m_appHandle, m_pin, UKEY_CONTAINER_NAME, &m_retryCount, &m_containerHandle); -+ ulReval = m_driver->createApplication(devHandle, pin, UKEY_APP_NAME, appHandle); - if (ulReval != SAR_OK) - { -- KLOG_ERROR() << "open container failed:" << m_driver->getErrorReason(ulReval); -+ KLOG_ERROR() << "create application failed:" << m_driver->getErrorReason(ulReval) -+ << " device serial number:" << deviceSerialNumber(); - return ulReval; - } -- KLOG_DEBUG() << "open container success"; -- -- ulReval = m_driver->genECCKeyPair(m_containerHandle, publicKey); -- -+ KLOG_DEBUG() << "create application suceess"; -+ ulReval = m_driver->createContainer(*appHandle, pin, UKEY_CONTAINER_NAME, &m_retryCount, containerHandle); - return ulReval; - } - --bool UKeyFTDevice::isExistsApplication(const QString &appName) -+bool UKeyFTDevice::isExistBinding() - { -- QString appNames = m_driver->enumApplication(m_devHandle); -+ QStringList featureIDs = FeatureDB::getInstance()->getFeatureIDs(deviceInfo().idVendor, deviceInfo().idProduct, deviceType(), deviceSerialNumber()); -+ for (auto id : featureIDs) -+ { -+ FeatureInfo info = FeatureDB::getInstance()->getFeatureInfo(id); -+ if (info.deviceSerialNumber == deviceSerialNumber()) -+ { -+ KLOG_DEBUG() << QString("Exist Binding: feature id:%1, device serial number: %2").arg(id).arg(deviceSerialNumber()); -+ return true; -+ } -+ } -+ return false; -+} -+ -+bool UKeyFTDevice::isExistsApplication(DEVHANDLE devHandle, const QString &appName) -+{ -+ QString appNames = m_driver->enumApplication(devHandle); - KLOG_DEBUG() << "enum app names:" << appNames; - if (appNames.contains(appName)) - { -@@ -225,8 +238,8 @@ void UKeyFTDevice::doingIdentifyStart(const QString &value) - KLOG_DEBUG() << "ukey identify start"; - QJsonValue ukeyValue = Utils::getValueFromJsonString(value, AUTH_DEVICE_JSON_KEY_UKEY); - auto jsonObject = ukeyValue.toObject(); -- m_pin = jsonObject.value(AUTH_DEVICE_JSON_KEY_PIN).toString(); -- if (m_pin.isEmpty()) -+ QString pin = jsonObject.value(AUTH_DEVICE_JSON_KEY_PIN).toString(); -+ if (pin.isEmpty()) - { - QString message = tr("The pin code cannot be empty!"); - Q_EMIT m_dbusAdaptor->IdentifyStatus("", IDENTIFY_STATUS_NOT_MATCH, message); -@@ -239,29 +252,38 @@ void UKeyFTDevice::doingIdentifyStart(const QString &value) - DeviceInfo deviceInfo = this->deviceInfo(); - if (m_identifyIDs.isEmpty()) - { -- saveList = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct, deviceType()); -+ saveList = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct, deviceType(), deviceSerialNumber()); - } - else - { - Q_FOREACH (auto id, m_identifyIDs) - { - QByteArray feature = FeatureDB::getInstance()->getFeature(id); -- if (!feature.isEmpty()) -- saveList << feature; -+ saveList << feature; - } - } - -- if (saveList.count() != 0) -+ if (saveList.count() == 0) - { -- for (int j = 0; j < saveList.count(); j++) -- { -- auto saveTemplate = saveList.value(j); -- identifyKeyFeature(saveTemplate); -- } -+ KLOG_DEBUG() << "no found feature id"; -+ notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH); -+ internalStopIdentify(); -+ return; - } -- else -+ -+ m_driver = new UKeySKFDriver(); -+ if (!m_driver->loadLibrary(FT_UKEY_DRIVER_LIB)) - { -- KLOG_DEBUG() << "no found feature id"; -+ KLOG_ERROR() << "load library failed"; -+ notifyUKeyEnrollProcess(ENROLL_PROCESS_FAIL); -+ internalStopIdentify(); -+ return; -+ } -+ -+ for (int j = 0; j < saveList.count(); j++) -+ { -+ auto savedKey = saveList.value(j); -+ identifyKeyFeature(pin,savedKey); - } - - internalStopIdentify(); -@@ -271,10 +293,14 @@ void UKeyFTDevice::internalStopEnroll() - { - if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL) - { -- closeUkey(); -- m_pin.clear(); - setDeviceStatus(DEVICE_STATUS_IDLE); - clearWatchedServices(); -+ if (m_driver) -+ { -+ KLOG_DEBUG() << "delete m_driver"; -+ delete m_driver; -+ m_driver = nullptr; -+ } - KLOG_DEBUG() << "stop Enroll"; - } - } -@@ -283,58 +309,48 @@ void UKeyFTDevice::internalStopIdentify() - { - if (deviceStatus() == DEVICE_STATUS_DOING_IDENTIFY) - { -- closeUkey(); - m_identifyIDs.clear(); -- m_pin.clear(); - setDeviceStatus(DEVICE_STATUS_IDLE); - clearWatchedServices(); -+ if (m_driver) -+ { -+ delete m_driver; -+ m_driver = nullptr; -+ } - KLOG_DEBUG() << "stopIdentify"; - } - } - --void UKeyFTDevice::closeUkey() -+void UKeyFTDevice::resetUkey() - { -- if (!m_driver->isLoaded()) -- { -- return; -- } -- if (m_containerHandle) -- { -- m_driver->closeContainer(m_containerHandle); -- m_containerHandle = nullptr; -- } -- -- if (m_appHandle) -- { -- m_driver->closeApplication(m_appHandle); -- m_appHandle = nullptr; -- } -- -- if (m_devHandle) -- { -- m_driver->disConnectDev(m_devHandle); -- m_devHandle = nullptr; -- } -+ UKeySKFDriver driver; -+ driver.loadLibrary(FT_UKEY_DRIVER_LIB); -+ DEVHANDLE devHandle = driver.connectDev(deviceSerialNumber()); -+ driver.resetUkey(devHandle); -+ KLOG_DEBUG() << "resetUkey"; - } - --void UKeyFTDevice::identifyKeyFeature(QByteArray keyFeature) -+void UKeyFTDevice::identifyKeyFeature(const QString &pin, QByteArray keyFeature) - { -- DEVHANDLE m_devHandle = m_driver->connectDev(); -- if (!m_devHandle) -+ DEVHANDLE devHandle = m_driver->connectDev(deviceSerialNumber()); -+ if (!devHandle) - { - notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH); - return; - } - - ULONG ret; -- ret = m_driver->onOpenApplication(m_devHandle, (LPSTR)UKEY_APP_NAME, &m_appHandle); -+ HAPPLICATION appHandle; -+ HCONTAINER containerHandle; -+ -+ ret = m_driver->onOpenApplication(devHandle, (LPSTR)UKEY_APP_NAME, &appHandle); - if (ret != SAR_OK) - { - notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH, ret); - return; - } - -- ret = m_driver->onOpenContainer(m_appHandle, m_pin, UKEY_CONTAINER_NAME, &m_retryCount, &m_containerHandle); -+ ret = m_driver->onOpenContainer(appHandle, pin, UKEY_CONTAINER_NAME, &m_retryCount, &containerHandle); - if (ret != SAR_OK) - { - notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH, ret); -@@ -342,7 +358,7 @@ void UKeyFTDevice::identifyKeyFeature(QByteArray keyFeature) - } - - ECCSIGNATUREBLOB Signature = {0}; -- ret = m_driver->authSignData(m_containerHandle, m_devHandle, Signature); -+ ret = m_driver->authSignData(containerHandle, devHandle, Signature); - if (ret != SAR_OK) - { - KLOG_DEBUG() << "auth sign data failed:" << m_driver->getErrorReason(ret); -@@ -358,7 +374,7 @@ void UKeyFTDevice::identifyKeyFeature(QByteArray keyFeature) - memcpy(eccPubKey.XCoordinate, (unsigned char *)xCoordinateArray.data(), ECC_MAX_XCOORDINATE_BITS_LEN / 8); - memcpy(eccPubKey.YCoordinate, (unsigned char *)yCoordinateArray.data(), ECC_MAX_YCOORDINATE_BITS_LEN / 8); - -- ret = m_driver->verifyData(m_devHandle, Signature, eccPubKey); -+ ret = m_driver->verifyData(devHandle, Signature, eccPubKey); - if (ret != SAR_OK) - { - KLOG_DEBUG() << "verify data failed:" << m_driver->getErrorReason(ret); -@@ -373,10 +389,15 @@ void UKeyFTDevice::identifyKeyFeature(QByteArray keyFeature) - - void UKeyFTDevice::notifyUKeyEnrollProcess(EnrollProcess process, ULONG error, const QString &featureID) - { -- QString message, reason; -+ QString reason; - // 目前只需要返回有关pin码的错误信息 - reason = getPinErrorReson(error); -+ if (error != SAR_OK) -+ { -+ KLOG_DEBUG() << "Ukey Error Reason:" << m_driver->getErrorReason(error); -+ } - -+ QString message = tr("Binding user failed!"); - switch (process) - { - case ENROLL_PROCESS_SUCCESS: -@@ -384,18 +405,16 @@ void UKeyFTDevice::notifyUKeyEnrollProcess(EnrollProcess process, ULONG error, c - Q_EMIT m_dbusAdaptor->EnrollStatus(featureID, 100, ENROLL_STATUS_COMPLETE, message); - break; - case ENROLL_PROCESS_FAIL: -- message = tr("Binding user failed!"); - if (!reason.isEmpty()) - { - message.append(reason); - } - Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_STATUS_FAIL, message); -- KLOG_DEBUG() << "Ukey Error Reason:" << m_driver->getErrorReason(error); - break; - case ENROLL_PROCESS_REPEATED_ENROLL: -- message = tr("UKey has been bound"); -- Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_STATUS_REPEATED, message); -+ message.append(tr("UKey has been bound")); - Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_STATUS_FAIL, message); -+ break; - default: - break; - } -diff --git a/src/device/ukey/ukey-ft-device.h b/src/device/ukey/ukey-ft-device.h -index b74a24e..87d8c45 100644 ---- a/src/device/ukey/ukey-ft-device.h -+++ b/src/device/ukey/ukey-ft-device.h -@@ -14,10 +14,11 @@ - - #pragma once - #include -+#include -+#include - #include "device/auth-device.h" - #include "driver/ukey/ukey-skf-driver.h" - #include "ukey-skf.h" --#include - - namespace Kiran - { -@@ -30,33 +31,34 @@ public: - - bool initDriver() override; - -+ void resetUkey(); -+ -+private Q_SLOTS: -+ bool initSerialNumber(); -+ - private: - void doingEnrollStart(const QString &extraInfo) override; - void doingIdentifyStart(const QString &value) override; -- -+ - void internalStopEnroll() override; - void internalStopIdentify() override; - -- void identifyKeyFeature(QByteArray keyFeature); -- -- void bindingUKey(); -- ULONG genKeyPair(ECCPUBLICKEYBLOB *publicKey); -- bool isExistPublicKey(); -- bool isExistsApplication(const QString &appName); -+ void identifyKeyFeature(const QString &pin, QByteArray keyFeature); - -+ void bindingUKey(DEVHANDLE devHandle, const QString &pin); -+ ULONG createContainer(const QString &pin, DEVHANDLE devHandle, HAPPLICATION *appHandle, HCONTAINER *containerHandle); -+ bool isExistsApplication(DEVHANDLE devHandle, const QString &appName); -+ bool isExistBinding(); - void notifyUKeyEnrollProcess(EnrollProcess process, ULONG error = SAR_OK, const QString &featureID = QString()); - void notifyUKeyIdentifyProcess(IdentifyProcess process, ULONG error = SAR_OK, const QString &featureID = QString()); - - QString getPinErrorReson(ULONG error); - -- void closeUkey(); - private: -- DEVHANDLE m_devHandle; -- HAPPLICATION m_appHandle; -- HCONTAINER m_containerHandle; - ULONG m_retryCount = 1000000; -- QString m_pin; -- QSharedPointer m_driver; -+ UKeySKFDriver *m_driver = nullptr; -+ static QStringList m_existingSerialNumber; -+ QTimer m_reInitSerialNumberTimer; - }; - - } // namespace Kiran -diff --git a/src/driver/multi-function/mf-iristar-driver.cpp b/src/driver/multi-function/mf-iristar-driver.cpp -index 298a0e8..ae8a389 100644 ---- a/src/driver/multi-function/mf-iristar-driver.cpp -+++ b/src/driver/multi-function/mf-iristar-driver.cpp -@@ -376,7 +376,7 @@ int MFIriStarDriver::startIdentify(QStringList featureIDs) - - if (featureIDs.isEmpty()) - { -- saveList = FeatureDB::getInstance()->getFeatures(m_idVendor, m_idProduct, (DeviceType)m_currentDeviceType); -+ saveList = FeatureDB::getInstance()->getFeatures(m_idVendor, m_idProduct, (DeviceType)m_currentDeviceType,QString()); - } - else - { -diff --git a/src/driver/ukey/ukey-skf-driver.cpp b/src/driver/ukey/ukey-skf-driver.cpp -index dd74772..e1e89d7 100644 ---- a/src/driver/ukey/ukey-skf-driver.cpp -+++ b/src/driver/ukey/ukey-skf-driver.cpp -@@ -180,46 +180,123 @@ bool UKeySKFDriver::isLoaded() - return m_driverLib->isLoaded; - } - --DEVHANDLE UKeySKFDriver::connectDev() -+QStringList UKeySKFDriver::enumDevName() - { - ULONG ulBufSize = 0; - ULONG ulReval = m_driverLib->SKF_EnumDev(TRUE, NULL, &ulBufSize); - if (ulReval != SAR_OK) - { - KLOG_DEBUG() << "Enum Dev error:" << getErrorReason(ulReval); -- return nullptr; -+ return QStringList(); - } - - LPSTR szNameList = (LPSTR)malloc(ulBufSize * sizeof(CHAR)); - memset(szNameList, '\0', ulBufSize); - ulReval = m_driverLib->SKF_EnumDev(TRUE, szNameList, &ulBufSize); -- if (ulReval == SAR_OK) -+ if ((ulReval != SAR_OK)) -+ { -+ KLOG_DEBUG() << "Enum Dev error:" << getErrorReason(ulReval); -+ free(szNameList); -+ return QStringList(); -+ } -+ -+ LPSTR pszTemp = szNameList; -+ if (NULL == pszTemp) -+ { -+ KLOG_DEBUG() << "no found ukey device"; -+ free(szNameList); -+ return QStringList(); -+ } -+ -+ QStringList nameList; -+ while ((*pszTemp != '\0') && (*(pszTemp + 1) != '\0')) - { -- LPSTR pszTemp = szNameList; -- if (NULL == pszTemp) -+ nameList << QString::fromLatin1((const char *)pszTemp, strlen((const char *)pszTemp)); -+ pszTemp += strlen((const char *)pszTemp) + 1; -+ } -+ KLOG_DEBUG() << "device name list:" << nameList; -+ -+ free(szNameList); -+ return nameList; -+} -+ -+QStringList UKeySKFDriver::enumDevSerialNumber() -+{ -+ QStringList devNameList = enumDevName(); -+ QStringList serialNumberList; -+ for (auto devName : devNameList) -+ { -+ DEVHANDLE devHandle; -+ ULONG pulDevState; -+ QByteArray devNameArray = devName.toLatin1(); -+ unsigned char *szDevName = (unsigned char *)devNameArray.data(); -+ ULONG ulReval = m_driverLib->SKF_ConnectDev(szDevName, &devHandle); -+ if (SAR_OK != ulReval) - { -- KLOG_DEBUG() << "no found ukey device"; -- return nullptr; -+ continue; - } -- while (*pszTemp != '\0') -+ DEVINFO devInfo; -+ m_driverLib->SKF_GetDevInfo(devHandle, &devInfo); -+ serialNumberList << QString((const char *)devInfo.SerialNumber); -+ m_driverLib->SKF_DisConnectDev(devHandle); -+ } -+ KLOG_DEBUG() << "dev serial number list:" << serialNumberList; -+ return serialNumberList; -+} -+ -+DEVHANDLE UKeySKFDriver::connectDev() -+{ -+ QStringList devNameList = enumDevName(); -+ for (auto devName : devNameList) -+ { -+ DEVHANDLE devHandle; -+ ULONG pulDevState; -+ QByteArray devNameArray = devName.toLatin1(); -+ unsigned char *szDevName = (unsigned char *)devNameArray.data(); -+ ULONG ulReval = m_driverLib->SKF_ConnectDev(szDevName, &devHandle); -+ if (SAR_OK == ulReval) - { -- DEVHANDLE devHandle; -- ulReval = m_driverLib->SKF_ConnectDev(pszTemp, &devHandle); -- if (SAR_OK == ulReval) -- { -- return devHandle; -- } -- else -- { -- KLOG_ERROR() << "Connect Dev failed:" << getErrorReason(ulReval); -- } -- pszTemp += strlen((const char *)pszTemp) + 1; -+ KLOG_DEBUG() << "connect dev success"; -+ return devHandle; -+ } -+ else -+ { -+ KLOG_ERROR() << "Connect Dev failed:" << getErrorReason(ulReval); - } - } -- free(szNameList); -+ - return nullptr; - } - -+DEVHANDLE UKeySKFDriver::connectDev(const QString &serialNumber) -+{ -+ QStringList devNameList = enumDevName(); -+ for (auto devName : devNameList) -+ { -+ DEVHANDLE devHandle; -+ QByteArray devNameArray = devName.toLatin1(); -+ unsigned char *szDevName = (unsigned char *)devNameArray.data(); -+ ULONG ulReval = m_driverLib->SKF_ConnectDev(szDevName, &devHandle); -+ if (ulReval != SAR_OK) -+ { -+ KLOG_ERROR() << QString("Connect Dev %1 failed:").arg(devName) << getErrorReason(ulReval); -+ continue; -+ } -+ -+ DEVINFO devInfo; -+ m_driverLib->SKF_GetDevInfo(devHandle, &devInfo); -+ if (serialNumber == QString((const char *)devInfo.SerialNumber)) -+ { -+ KLOG_DEBUG() << QString("Connect Dev %1 success, SerialNumber: %2").arg(devName).arg(serialNumber); -+ return devHandle; -+ } -+ else -+ { -+ m_driverLib->SKF_DisConnectDev(devHandle); -+ } -+ } -+ return nullptr; -+} - - void UKeySKFDriver::deleteAllApplication(DEVHANDLE devHandle) - { -@@ -262,6 +339,23 @@ QString UKeySKFDriver::enumApplication(DEVHANDLE devHandle) - } - } - -+bool UKeySKFDriver::isExistPublicKey(HCONTAINER containerHandle) -+{ -+ unsigned char *pPubKey = NULL; -+ ULONG ulPubKeyLen = 0; -+ ULONG ret = m_driverLib->SKF_ExportPublicKey(containerHandle, TRUE, pPubKey, &ulPubKeyLen); -+ pPubKey = (unsigned char *)malloc(ulPubKeyLen); -+ ret = m_driverLib->SKF_ExportPublicKey(containerHandle, TRUE, pPubKey, &ulPubKeyLen); -+ if (ret == SAR_OK) -+ { -+ return true; -+ } -+ else -+ { -+ return false; -+ } -+} -+ - ULONG UKeySKFDriver::devAuth(DEVHANDLE devHandle) - { - BYTE random[16] = {0}; -@@ -340,7 +434,8 @@ void UKeySKFDriver::closeContainer(HCONTAINER containerHandle) - - void UKeySKFDriver::disConnectDev(DEVHANDLE devHandle) - { -- m_driverLib->SKF_DisConnectDev(devHandle); -+ ULONG ret = m_driverLib->SKF_DisConnectDev(devHandle); -+ KLOG_DEBUG() << "getErrorReason(ret):" << getErrorReason(ret); - } - - ULONG UKeySKFDriver::createApplication(DEVHANDLE devHandle, QString pin, QString appName, HAPPLICATION *appHandle) -@@ -527,6 +622,18 @@ ULONG UKeySKFDriver::unblockPin(DEVHANDLE devHandle, const QString &adminPin, co - return ulReval; - } - -+ULONG UKeySKFDriver::resetUkey(DEVHANDLE devHandle) -+{ -+ ULONG ulReval = devAuth(devHandle); -+ if (ulReval != SAR_OK) -+ { -+ KLOG_ERROR() << "Device authentication failed"; -+ return ulReval; -+ } -+ deleteAllApplication(devHandle); -+ return ulReval; -+} -+ - QString UKeySKFDriver::getErrorReason(ULONG error) - { - for (int i = 0; i < sizeof(skf_errors) / sizeof(skf_errors[0]); i++) -diff --git a/src/driver/ukey/ukey-skf-driver.h b/src/driver/ukey/ukey-skf-driver.h -index 0d45c7a..058dc63 100644 ---- a/src/driver/ukey/ukey-skf-driver.h -+++ b/src/driver/ukey/ukey-skf-driver.h -@@ -29,10 +29,15 @@ public: - bool isLoaded(); - bool loadLibrary(QString libPath); - -+ QStringList enumDevName(); -+ QStringList enumDevSerialNumber(); - DEVHANDLE connectDev(); - -+ DEVHANDLE connectDev(const QString &serialNumber); -+ - void deleteAllApplication(DEVHANDLE devHandle); - QString enumApplication(DEVHANDLE devHandle); -+ bool isExistPublicKey(HCONTAINER containerHandle); - - ULONG devAuth(DEVHANDLE devHandle); - ULONG onOpenApplication(DEVHANDLE hDev, LPSTR szAppName, HAPPLICATION *appHandle); -@@ -52,14 +57,15 @@ public: - - ULONG changePin(DEVHANDLE devHandle, int userType, const QString ¤tPin, const QString &newPin, ULONG *retryCount); - -- -- - ULONG unblockPin(DEVHANDLE devHandle, const QString &adminPin, const QString &newUserPin, ULONG *retryCount); - -+ ULONG resetUkey(DEVHANDLE devHandle); -+ - QString getErrorReason(ULONG error); - - QString getDefaultValueFromConf(const QString &key); - -+ - private: - QSharedPointer m_driverLib; - HANDLE m_libHandle; -diff --git a/src/feature-db.cpp b/src/feature-db.cpp -index 10d23ca..ee0a4bd 100644 ---- a/src/feature-db.cpp -+++ b/src/feature-db.cpp -@@ -72,7 +72,8 @@ bool FeatureDB::createDBConnection() - "feature BLOB NOT NULL," - "idVendor TEXT," - "idProduct TEXT," -- "deviceType INT);"); -+ "deviceType INT," -+ "deviceSerialNumber TEXT);"); - - if (!query.exec(createTable)) - { -@@ -82,15 +83,16 @@ bool FeatureDB::createDBConnection() - return true; - } - --bool FeatureDB::addFeature(const QString &featureID, QByteArray feature, DeviceInfo deviceInfo, DeviceType deviceType) -+bool FeatureDB::addFeature(const QString &featureID, QByteArray feature, DeviceInfo deviceInfo, DeviceType deviceType, const QString &deviceSerialNumber) - { - QSqlQuery query(m_database); -- query.prepare("INSERT into feature(featureID, feature, idVendor, idProduct, deviceType) VALUES(:featureID, :feature,:idVendor, :idProduct, :deviceType) ;"); -+ query.prepare("INSERT into feature(featureID, feature, idVendor, idProduct, deviceType, deviceSerialNumber) VALUES(:featureID, :feature,:idVendor, :idProduct, :deviceType, :deviceSerialNumber) ;"); - query.bindValue(":featureID", featureID); - query.bindValue(":feature", feature); - query.bindValue(":idVendor", deviceInfo.idVendor); - query.bindValue(":idProduct", deviceInfo.idProduct); - query.bindValue(":deviceType", (int)deviceType); -+ query.bindValue(":deviceSerialNumber", deviceSerialNumber); - return query.exec(); - } - -@@ -116,13 +118,14 @@ QByteArray FeatureDB::getFeature(const QString &featureID) - return QByteArray(); - } - --QList FeatureDB::getFeatures(const QString &idVendor, const QString &idProduct, DeviceType deviceType) -+QList FeatureDB::getFeatures(const QString &idVendor, const QString &idProduct, DeviceType deviceType, const QString &deviceSerialNumber) - { - QSqlQuery query(m_database); -- query.prepare("SELECT feature FROM feature WHERE idVendor = :Vid AND idProduct = :Pid AND deviceType = :devType"); -+ query.prepare("SELECT feature FROM feature WHERE idVendor = :Vid AND idProduct = :Pid AND deviceType = :devType AND deviceSerialNumber = :serialNumber"); - query.bindValue(":Vid", idVendor); - query.bindValue(":Pid", idProduct); - query.bindValue(":devType", (int)deviceType); -+ query.bindValue(":serialNumber", deviceSerialNumber); - query.exec(); - QByteArrayList featuresList; - while (query.next()) -@@ -147,13 +150,14 @@ QList FeatureDB::getAllFeatures() - return featuresList; - } - --QStringList FeatureDB::getFeatureIDs(const QString &idVendor, const QString &idProduct, DeviceType deviceType) -+QStringList FeatureDB::getFeatureIDs(const QString &idVendor, const QString &idProduct, DeviceType deviceType, const QString &deviceSerialNumber) - { - QSqlQuery query(m_database); -- query.prepare("SELECT featureID FROM feature WHERE idVendor = :Vid AND idProduct = :Pid AND deviceType = :devType"); -+ query.prepare("SELECT featureID FROM feature WHERE idVendor = :Vid AND idProduct = :Pid AND deviceType = :devType AND deviceSerialNumber = :serialNumber"); - query.bindValue(":Vid", idVendor); - query.bindValue(":Pid", idProduct); - query.bindValue(":devType", (int)deviceType); -+ query.bindValue(":serialNumber", deviceSerialNumber); - query.exec(); - QStringList featureIDs; - while (query.next()) -@@ -192,6 +196,24 @@ QStringList FeatureDB::getAllFeatureIDs() - return featureIDs; - } - -+FeatureInfo FeatureDB::getFeatureInfo(const QString &featureID) -+{ -+ QSqlQuery query(m_database); -+ query.prepare("SELECT idVendor, idProduct, deviceType, deviceSerialNumber FROM feature WHERE featureID = :id"); -+ query.bindValue(":id", featureID); -+ query.exec(); -+ FeatureInfo featureInfo; -+ if (query.next()) -+ { -+ featureInfo.id = featureID; -+ featureInfo.idVendor = query.value("idVendor").toString(); -+ featureInfo.idProduct = query.value("idProduct").toString(); -+ featureInfo.deviceType = query.value("deviceType").toInt(); -+ featureInfo.deviceSerialNumber = query.value("deviceSerialNumber").toString(); -+ } -+ return featureInfo; -+} -+ - bool FeatureDB::updateFeature(const QString &featureID, QByteArray newFeature) - { - QSqlQuery query(m_database); -@@ -212,7 +234,9 @@ bool FeatureDB::contains(const QString &featureID) - return true; - } - else -+ { - return false; -+ } - } - - } // namespace Kiran -diff --git a/src/feature-db.h b/src/feature-db.h -index 8acad2e..2f2623e 100644 ---- a/src/feature-db.h -+++ b/src/feature-db.h -@@ -20,26 +20,38 @@ - - namespace Kiran - { -+struct FeatureInfo -+{ -+ QString id; -+ QString idVendor; -+ QString idProduct; -+ int deviceType; -+ QString deviceSerialNumber; -+}; -+ - class FeatureDB - { - public: - explicit FeatureDB(); - ~FeatureDB(); - -- static FeatureDB *getInstance() {return m_instance;}; -+ static FeatureDB *getInstance() { return m_instance; }; - static void globalInit(); -- static void globalDeinit() {delete m_instance;}; -- -+ static void globalDeinit() { delete m_instance; }; -+ - bool createDBConnection(); -- bool addFeature(const QString &featureID, QByteArray feature, DeviceInfo deviceInfo, DeviceType deviceType); -+ bool addFeature(const QString &featureID, QByteArray feature, -+ DeviceInfo deviceInfo, DeviceType deviceType, -+ const QString &deviceSerialNumber = QString()); - bool deleteFeature(const QString &featureID); - - QByteArray getFeature(const QString &featureID); -- QList getFeatures(const QString &idVendor,const QString &idProduct, DeviceType deviceType); -+ QList getFeatures(const QString &idVendor, const QString &idProduct, DeviceType deviceType, const QString &deviceSerialNumber); - QList getAllFeatures(); -- QStringList getFeatureIDs(const QString &idVendor,const QString &idProduct, DeviceType deviceType); -+ QStringList getFeatureIDs(const QString &idVendor, const QString &idProduct, DeviceType deviceType, const QString &deviceSerialNumber); - QString getFeatureID(QByteArray feature); - QStringList getAllFeatureIDs(); -+ FeatureInfo getFeatureInfo(const QString &featureID); - - bool updateFeature(const QString &featureID, QByteArray newFeature); - -diff --git a/ukey-manager/ukey-manager.cpp b/ukey-manager/ukey-manager.cpp -index 125374f..cb7a0eb 100644 ---- a/ukey-manager/ukey-manager.cpp -+++ b/ukey-manager/ukey-manager.cpp -@@ -59,29 +59,8 @@ bool UkeyManager::initDriver() - - ULONG UkeyManager::resetUkey() - { -- ULONG ulReval = m_driver->devAuth(m_devHandle); -- if (ulReval != SAR_OK) -- { -- KLOG_ERROR() << "Device authentication failed"; -- return ulReval; -- } -- m_driver->deleteAllApplication(m_devHandle); -- -- ulReval = m_driver->createApplication(m_devHandle, DEFAULT_USER_PINCODE, UKEY_APP_NAME, &m_appHandle); -- if (ulReval != SAR_OK) -- { -- KLOG_ERROR() << "create application failed:" << m_driver->getErrorReason(ulReval); -- return ulReval; -- } -- KLOG_DEBUG() << "create application suceess"; -- ulReval = m_driver->createContainer(m_appHandle, DEFAULT_USER_PINCODE, UKEY_CONTAINER_NAME, &m_retryCount, &m_containerHandle); -- if (ulReval != SAR_OK) -- { -- KLOG_ERROR() << "create container failed:" << m_driver->getErrorReason(ulReval); -- return ulReval; -- } -- KLOG_DEBUG() << "create new container success"; -- -+ ULONG ulReval = m_driver->resetUkey(m_devHandle); -+ m_driver->disConnectDev(m_devHandle); - return ulReval; - } - -@@ -102,7 +81,6 @@ ULONG UkeyManager::changePin(const QString &userType, const QString ¤tPin, - std::cout << "invalid user type" << std::endl; - return SAR_FAIL; - } -- KLOG_DEBUG() << "m_appHandle:" << m_appHandle; - KLOG_DEBUG() << "type:" << type; - ULONG ret = m_driver->changePin(m_devHandle, type, currentPin, newPin, retryCount); - return ret; --- -2.33.0 - diff --git a/0002-fix-ukey-manager-Fixed-a-crash-caused-by-repeatedly-.patch b/0002-fix-ukey-manager-Fixed-a-crash-caused-by-repeatedly-.patch new file mode 100644 index 0000000..43110c7 --- /dev/null +++ b/0002-fix-ukey-manager-Fixed-a-crash-caused-by-repeatedly-.patch @@ -0,0 +1,30 @@ +From 5b329ca4e7610efb23b7077d5af201290bd3fb61 Mon Sep 17 00:00:00 2001 +From: luoqing +Date: Mon, 3 Jul 2023 16:41:09 +0800 +Subject: [PATCH 2/6] fix(ukey-manager):Fixed a crash caused by repeatedly + calling disConnectDev in resetUkey and destructor to release the same device + handle +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +- 修复在resetUkey和析构函数中重复调用disConnectDev释放相同的设备句柄从而导致崩溃的问题 +--- + ukey-manager/ukey-manager.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/ukey-manager/ukey-manager.cpp b/ukey-manager/ukey-manager.cpp +index 30cd23a..d815d72 100644 +--- a/ukey-manager/ukey-manager.cpp ++++ b/ukey-manager/ukey-manager.cpp +@@ -62,7 +62,6 @@ bool UkeyManager::initDriver() + ULONG UkeyManager::resetUkey() + { + ULONG ulReval = m_driver->resetUkey(m_devHandle); +- m_driver->disConnectDev(m_devHandle); + return ulReval; + } + +-- +2.33.0 + diff --git a/0003-refactor-Modify-the-code-according-to-the-https-gite.patch b/0003-refactor-Modify-the-code-according-to-the-https-gite.patch new file mode 100644 index 0000000..f0a0164 --- /dev/null +++ b/0003-refactor-Modify-the-code-according-to-the-https-gite.patch @@ -0,0 +1,1752 @@ +From fe1104186ef3fe8978b75b01960cd147c99665bf Mon Sep 17 00:00:00 2001 +From: luoqing +Date: Thu, 6 Jul 2023 14:41:24 +0800 +Subject: [PATCH 3/6] refactor(*):Modify the code according to the + https://gitee.com/openeuler/kiran-authentication-devices/pulls/17 review + opinions +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +- 根据 https://gitee.com/openeuler/kiran-authentication-devices/pulls/17 评审意见修改代码 +--- + CMakeLists.txt | 7 +- + data/CMakeLists.txt | 14 +- + data/config.h.in | 5 +- + data/device.conf | 26 ++-- + data/driver.conf | 5 +- + include/auth-enum.h | 5 - + src/auth-device-manager.cpp | 130 +++++++++--------- + src/auth-device-manager.h | 10 +- + src/config-helper.cpp | 67 ++++++--- + src/config-helper.h | 15 +- + src/device/auth-device.cpp | 86 ++++++++++-- + src/device/auth-device.h | 30 ++-- + src/device/bio-device.cpp | 47 +++---- + src/device/bio-device.h | 18 +-- + src/device/device-creator.cpp | 19 +-- + src/device/device-creator.h | 2 +- + src/device/finger-vein/fv-sd-device.cpp | 32 +---- + src/device/finger-vein/fv-sd-device.h | 4 +- + src/device/fingerprint/fp-zk-device.cpp | 30 +--- + src/device/fingerprint/fp-zk-device.h | 4 +- + .../multi-function/mf-iristar-device.cpp | 32 ++--- + src/device/multi-function/mf-iristar-device.h | 7 +- + src/device/ukey/ukey-skf-device.cpp | 77 +++-------- + src/device/ukey/ukey-skf-device.h | 6 +- + .../multi-function/mf-iristar-driver.cpp | 71 +++------- + src/driver/multi-function/mf-iristar-driver.h | 8 +- + src/feature-db.cpp | 2 +- + src/utils.cpp | 29 +--- + src/utils.h | 3 - + ukey-manager/ukey-manager.cpp | 1 + + 30 files changed, 359 insertions(+), 433 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 122d4be..d1189aa 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -19,9 +19,14 @@ set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + set(TRANSLATION_INSTALL_DIR ${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME}/translations) +-configure_file(${CMAKE_SOURCE_DIR}/data/config.h.in ${CMAKE_BINARY_DIR}/config.h) ++set(CONF_INSTALL_DIR /${CMAKE_INSTALL_SYSCONFDIR}/${PROJECT_NAME}) + + add_subdirectory(src) + add_subdirectory(data) + add_subdirectory(ukey-manager) + ++ ++set(KAD_INSTALL_INCLUDE ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) ++ ++install(FILES ${PROJECT_SOURCE_DIR}/include/kiran-auth-device-i.h ++ DESTINATION ${KAD_INSTALL_INCLUDE}/) +diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt +index 44846ff..617cd14 100644 +--- a/data/CMakeLists.txt ++++ b/data/CMakeLists.txt +@@ -1,6 +1,5 @@ + cmake_minimum_required(VERSION 3.2) + +-set(SYSCONFDIR "/etc") + # init variables + if(SYSTEMD_FOUND) + pkg_get_variable(SYSTEM_UNIT_DIR systemd systemdsystemunitdir) +@@ -29,15 +28,16 @@ install(FILES ${PROJECT_BINARY_DIR}/data/com.kylinsec.Kiran.AuthDevice.service + DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/dbus-1/system-services) + + install(FILES ${PROJECT_SOURCE_DIR}/data/driver.conf +- DESTINATION ${SYSCONFDIR}/${PROJECT_NAME}) ++ DESTINATION ${CONF_INSTALL_DIR}) + + install(FILES ${PROJECT_SOURCE_DIR}/data/device.conf +- DESTINATION ${SYSCONFDIR}/${PROJECT_NAME}) ++ DESTINATION ${CONF_INSTALL_DIR}) + + install(FILES ${PROJECT_SOURCE_DIR}/data/ukey-manager.conf +- DESTINATION ${SYSCONFDIR}/${PROJECT_NAME}) ++ DESTINATION ${CONF_INSTALL_DIR}) + +-set(KAD_INSTALL_INCLUDE ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) ++set(DRIVER_CONF_PATH ${CONF_INSTALL_DIR}/driver.conf) ++set(DEVICE_CONF_PATH ${CONF_INSTALL_DIR}/device.conf) ++set(UKEY_MANAGER_CONF_PATH ${CONF_INSTALL_DIR}/ukey-manager.conf) + +-install(FILES ${PROJECT_SOURCE_DIR}/include/kiran-auth-device-i.h +- DESTINATION ${KAD_INSTALL_INCLUDE}/) +\ No newline at end of file ++configure_file(${CMAKE_SOURCE_DIR}/data/config.h.in ${CMAKE_BINARY_DIR}/config.h) +\ No newline at end of file +diff --git a/data/config.h.in b/data/config.h.in +index d0b327a..fb1f582 100644 +--- a/data/config.h.in ++++ b/data/config.h.in +@@ -1,3 +1,6 @@ + #pragma once + +-#define TRANSLATE_PREFIX "@TRANSLATION_INSTALL_DIR@" ++#define TRANSLATE_PREFIX "@TRANSLATION_INSTALL_DIR@" ++#define DEVICE_CONF "@DEVICE_CONF_PATH@" ++#define DRIVER_CONF "@DRIVER_CONF_PATH@" ++#define UKEY_MANAGER_CONF "@UKEY_MANAGER_CONF_PATH@" +\ No newline at end of file +diff --git a/data/device.conf b/data/device.conf +index 9a367bf..6d7d83b 100644 +--- a/data/device.conf ++++ b/data/device.conf +@@ -3,50 +3,40 @@ + #Name= + #设备类型 + #Type= +-#设备的标识VID,PID.通过标识可找到该设备属于哪种类型的设备 +-#Vid= ++#设备Id,由设备vid和pid组成,以,分隔组成列表。通过标识可找到该设备属于哪种类型的设备 ++#Id=vid1:pid1,vid2:pid2 + #Pid= + #驱动名,与driver.conf中的相对应 + #Driver= +-#对于UKey设备必填,其他类型设备选填:指定加载so的绝对路径;一般不用填,不填则加载默认so; +-#LibPath= + + [ZKFingerpint] + Name= ZKFingerpint + # 设备类型 + Type=0 + # 设备的标识VID,PID.通过标识可找到该设备属于哪种类型的设备 +-Vid=1b55 +-Pid=0120 ++Id=1b55:0120 + Driver= zkfp + + [sd] + Name=saintdeem + Type=2 +-Vid=05e3 +-Pid=0608 ++Id=05e3:0608 + Driver=sdfv + + [iristar] + Name=iristar + Type=1,3 +-Vid=04b4 +-Pid=00f3 ++Id=04b4:00f3 + Driver=irs_sdk2 + + [ft-skf] + Name=Feitian UKey + Type=5 +-Vid=096e +-Pid=0309 ++Id=096e:0309 + Driver=ukey-skf-ft +-LibPath=/usr/lib64/kiran-authentication-devices-sdk/ukey/skf/libes_3000gm.so + + [fish-skf] + Name=YuWeng UKey + Type=5 +-Vid=4612 +-Pid=04b4 +-Driver=ukey-skf-fish +-#对于UKey设备必填,其他类型设备选填:指定加载so的绝对路径;一般不用填,不填则加载默认so; +-LibPath=/usr/lib64/kiran-authentication-devices-sdk/ukey/skf/libGDBapi.so +\ No newline at end of file ++Id=4612:04b4 ++Driver=ukey-skf-fish +\ No newline at end of file +diff --git a/data/driver.conf b/data/driver.conf +index 917c9ab..135d592 100644 +--- a/data/driver.conf ++++ b/data/driver.conf +@@ -2,6 +2,7 @@ + #[DriverName] #驱动名,必填,与实际so对应 + #Enable #是否开启,必填,不填写则默认禁用 + #Type #设备类型,必填; 指纹-0;Refer to enum DeviceType in file /usr/include/kiran-authentication-device/kiran-auth-device-i.h ++#LibPath #对于UKey设备必填,其他类型设备选填:指定加载so的绝对路径;一般不用填,不填则加载默认so; + [zkfp] + Enable=true + Type=0 +@@ -20,7 +21,9 @@ Type=1,3 + [ukey-skf-ft] + Enable=true + Type=5 ++LibPath=/usr/lib64/kiran-authentication-devices-sdk/ukey/skf/libes_3000gm.so + + [ukey-skf-fish] + Enable=true +-Type=5 +\ No newline at end of file ++Type=5 ++LibPath=/usr/lib64/kiran-authentication-devices-sdk/ukey/skf/libGDBapi.so +\ No newline at end of file +diff --git a/include/auth-enum.h b/include/auth-enum.h +index 28d8853..b9e9e49 100644 +--- a/include/auth-enum.h ++++ b/include/auth-enum.h +@@ -19,11 +19,6 @@ namespace Kiran + { + #define DATABASE_DIR "/usr/share/kiran-authentication-devices" + +-#define DRIVER_BLACK_LIST_CONF "/etc/kiran-authentication-device/driver-blacklist.conf" +-#define DEVICE_CONF "/etc/kiran-authentication-devices/device.conf" +-#define DRIVER_CONF "/etc/kiran-authentication-devices/driver.conf" +-#define UKEY_MANAGER_CONF "/etc/kiran-authentication-devices/ukey-manager.conf" +- + #define AUTH_USER_ADMIN "com.kylinsec.kiran.authentication.user-administration" + + #define FT_UKEY_DRIVER_LIB "libes_3000gm.so" +diff --git a/src/auth-device-manager.cpp b/src/auth-device-manager.cpp +index 7cff6a6..a9f5fb0 100644 +--- a/src/auth-device-manager.cpp ++++ b/src/auth-device-manager.cpp +@@ -20,6 +20,7 @@ + #include + #include "auth_device_manager_adaptor.h" + #include "config-helper.h" ++#include "config.h" + #include "device/auth-device.h" + #include "device/device-creator.h" + #include "device/ukey/ukey-skf-device.h" +@@ -31,6 +32,9 @@ + + namespace Kiran + { ++ ++#define MAX_RETREY_CREATE_COUNT 2 ++ + AuthDeviceManager::AuthDeviceManager(QObject* parent) : QObject(parent) + { + } +@@ -180,15 +184,24 @@ void AuthDeviceManager::onRemove(const QDBusMessage& message, const QString& fea + void AuthDeviceManager::onSetEnableDriver(const QDBusMessage& message, const QString& driver_name, bool enable) + { + QStringList driverList = ConfigHelper::getDriverList(); +- if (!driverList.contains(driver_name)) +- { +- goto end; +- } +- ConfigHelper::setDriverEnabled(driver_name, enable); ++ QDBusMessage replyMessage; + +- // 驱动被禁用,将当前正在使用的设备释放掉 +- if (!enable) ++ do + { ++ if (!driverList.contains(driver_name)) ++ { ++ replyMessage = message.createErrorReply(QDBusError::Failed, "No driver with the corresponding name was found."); ++ break; ++ } ++ ConfigHelper::setDriverEnabled(driver_name, enable); ++ replyMessage = message.createReply(); ++ ++ if (enable) ++ { ++ break; ++ } ++ ++ // 驱动被禁用,将当前正在使用的设备释放掉 + auto devices = m_deviceMap.values(); + Q_FOREACH (AuthDevicePtr device, devices) + { +@@ -204,19 +217,55 @@ void AuthDeviceManager::onSetEnableDriver(const QDBusMessage& message, const QSt + Q_EMIT m_dbusAdaptor->DeviceDeleted(deviceType, deviceID); + KLOG_INFO() << QString("destroyed deviceType: %1, deviceID:%2").arg(deviceType).arg(deviceID); + } +- } +-end: +- auto replyMessage = message.createReply(); ++ } while (false); ++ + QDBusConnection::systemBus().send(replyMessage); + } + ++AuthDeviceList AuthDeviceManager::createDevices(const DeviceInfo& deviceInfo) ++{ ++ // TODO:先从内置默认支持的设备开始搜索,最后才搜索第三方设备 ++ QString vid = deviceInfo.idVendor; ++ QString pid = deviceInfo.idProduct; ++ if (!ConfigHelper::isDeviceSupported(vid, pid)) ++ { ++ KLOG_DEBUG() << "no auth device!" ++ << "idVendor:" << vid ++ << "idProduct:" << pid; ++ return AuthDeviceList(); ++ } ++ ++ DeviceConf deviceConf = ConfigHelper::getDeviceConf(vid, pid); ++ if (!ConfigHelper::driverEnabled(vid, pid)) ++ { ++ KLOG_INFO() << QString("driver:%1 is disabled, auth device: %2 can't be used") ++ .arg(deviceConf.driver) ++ .arg(deviceConf.deviceName) ++ << " vid:" << vid << " pid:" << pid; ++ return AuthDeviceList(); ++ } ++ ++ QString libPath = ConfigHelper::getLibPath(vid, pid); ++ DriverPtr driverPtr = DriverFactory::getInstance()->getDriver(deviceConf.driver, libPath); ++ ++ if (driverPtr.isNull()) ++ { ++ KLOG_ERROR() << QString("get driver: %1 failed!").arg(deviceConf.driver); ++ return AuthDeviceList(); ++ } ++ ++ AuthDeviceList deviceList = DeviceCereator::getInstance()->createDevices(vid, pid, driverPtr); ++ return deviceList; ++} ++ + CHECK_AUTH_WITH_1ARGS(AuthDeviceManager, Remove, onRemove, AUTH_USER_ADMIN, const QString&) + CHECK_AUTH_WITH_2ARGS(AuthDeviceManager, SetEnableDriver, onSetEnableDriver, AUTH_USER_ADMIN, const QString&, bool) + + void AuthDeviceManager::init() + { + m_dbusAdaptor = QSharedPointer(new AuthDeviceManagerAdaptor(this)); +- connect(&m_timer, &QTimer::timeout, this, &AuthDeviceManager::handleDeviceReCreate); ++ m_ReCreateTimer.setInterval(1000); ++ connect(&m_ReCreateTimer, &QTimer::timeout, this, &AuthDeviceManager::handleDeviceReCreate); + + QDBusConnection dbusConnection = QDBusConnection::systemBus(); + if (!dbusConnection.registerService(AUTH_DEVICE_DBUS_NAME)) +@@ -249,37 +298,7 @@ void AuthDeviceManager::init() + + void AuthDeviceManager::handleDeviceAdded(const DeviceInfo& deviceInfo) + { +- // TODO:先从内置默认支持的设备开始搜索,最后才搜索第三方设备 +- QString vid = deviceInfo.idVendor; +- QString pid = deviceInfo.idProduct; +- if (!ConfigHelper::isDeviceSupported(vid, pid)) +- { +- KLOG_DEBUG() << "no auth device!" +- << "idVendor:" << vid +- << "idProduct:" << pid; +- return; +- } +- +- DeviceConf deviceConf = ConfigHelper::getDeviceConf(vid, pid); +- if (!ConfigHelper::driverEnabled(vid, pid)) +- { +- KLOG_INFO() << QString("driver:%1 is disabled, auth device: %2 can't be used") +- .arg(deviceConf.driver) +- .arg(deviceConf.deviceName) +- << " vid:" << vid << " pid:" << pid; +- return; +- } +- +- QString libPath = ConfigHelper::getLibPath(vid, pid); +- DriverPtr driverPtr = DriverFactory::getInstance()->getDriver(deviceConf.driver, libPath); +- +- if (driverPtr.isNull()) +- { +- KLOG_ERROR() << QString("get driver: %1 failed!").arg(deviceConf.driver); +- return; +- } +- +- AuthDeviceList deviceList = DeviceCereator::getInstance()->getDevices(vid, pid, driverPtr); ++ AuthDeviceList deviceList = createDevices(deviceInfo); + if (deviceList.count() == 0) + { + handleDeviceCreateFail(deviceInfo); +@@ -289,7 +308,6 @@ void AuthDeviceManager::handleDeviceAdded(const DeviceInfo& deviceInfo) + Q_FOREACH (auto device, deviceList) + { + m_deviceMap.insert(deviceInfo.busPath, device); +- Q_EMIT this->DeviceAdded(device->deviceType(), device->deviceID()); + Q_EMIT m_dbusAdaptor->DeviceAdded(device->deviceType(), device->deviceID()); + + KLOG_DEBUG() << "auth device added" +@@ -304,9 +322,9 @@ void AuthDeviceManager::handleDeviceCreateFail(DeviceInfo deviceInfo) + m_retreyCreateDeviceMap.insert(deviceInfo, 0); + if (m_retreyCreateDeviceMap.count() != 0) + { +- if (!m_timer.isActive()) ++ if (!m_ReCreateTimer.isActive()) + { +- m_timer.start(1000); ++ m_ReCreateTimer.start(); + } + } + } +@@ -334,7 +352,7 @@ void AuthDeviceManager::handleDeviceDeleted() + deviceID = oldAuthDevice->deviceID(); + deviceType = oldAuthDevice->deviceType(); + int removeCount = m_deviceMap.remove(busPath); +- oldAuthDevice.clear(); ++ + Q_EMIT m_dbusAdaptor->DeviceDeleted(deviceType, deviceID); + + QMapIterator i(m_retreyCreateDeviceMap); +@@ -355,7 +373,7 @@ void AuthDeviceManager::handleDeviceReCreate() + { + if (m_retreyCreateDeviceMap.count() == 0) + { +- m_timer.stop(); ++ m_ReCreateTimer.stop(); + return; + } + +@@ -363,28 +381,15 @@ void AuthDeviceManager::handleDeviceReCreate() + while (i.hasNext()) + { + i.next(); +- if (i.value() >= 2) ++ if (i.value() >= MAX_RETREY_CREATE_COUNT) + { + m_retreyCreateDeviceMap.remove(i.key()); + continue; + } + + auto deviceInfo = i.key(); +- QString vid = deviceInfo.idVendor; +- QString pid = deviceInfo.idProduct; +- +- DeviceConf deviceConf = ConfigHelper::getDeviceConf(vid, pid); +- DriverPtr driverPtr = DriverFactory::getInstance()->getDriver(deviceConf.driver); +- if (!driverPtr.isNull()) +- { +- if (!driverPtr->initDriver()) +- { +- KLOG_ERROR() << QString("init driver %1 failed").arg(deviceConf.driver); +- continue; +- } +- } + +- AuthDeviceList deviceList = DeviceCereator::getInstance()->getDevices(vid, pid, driverPtr); ++ AuthDeviceList deviceList = createDevices(deviceInfo); + if (deviceList.count() == 0) + { + m_retreyCreateDeviceMap.insert(i.key(), i.value() + 1); +@@ -394,7 +399,6 @@ void AuthDeviceManager::handleDeviceReCreate() + Q_FOREACH (auto device, deviceList) + { + m_deviceMap.insert(deviceInfo.busPath, device); +- Q_EMIT this->DeviceAdded(device->deviceType(), device->deviceID()); + Q_EMIT m_dbusAdaptor->DeviceAdded(device->deviceType(), device->deviceID()); + + KLOG_DEBUG() << "device added" +diff --git a/src/auth-device-manager.h b/src/auth-device-manager.h +index 2253d50..95e68e9 100644 +--- a/src/auth-device-manager.h ++++ b/src/auth-device-manager.h +@@ -29,7 +29,6 @@ class AuthDeviceManagerAdaptor; + namespace Kiran + { + class AuthDevice; +-class ContextFactory; + + class AuthDeviceManager : public QObject, protected QDBusContext + { +@@ -53,7 +52,7 @@ public Q_SLOTS: + void Remove(const QString &feature_id); + + private Q_SLOTS: +- void handleDeviceAdded(const DeviceInfo &usbInfo); ++ void handleDeviceAdded(const DeviceInfo &deviceInfo); + void handleDeviceDeleted(); + void handleDeviceReCreate(); + void handleDeviceCreateFail(DeviceInfo deviceInfo); +@@ -62,10 +61,7 @@ private: + void init(); + void onRemove(const QDBusMessage &message, const QString &feature_id); + void onSetEnableDriver(const QDBusMessage &message, const QString &driver_name, bool enable); +- +-Q_SIGNALS: +- void DeviceAdded(int device_type, const QString &device_id); +- void DeviceDeleted(int device_type, const QString &device_id); ++ QList> createDevices(const DeviceInfo &deviceInfo); + + private: + static AuthDeviceManager *m_instance; +@@ -73,7 +69,7 @@ private: + QSharedPointer m_dbusAdaptor; + // 总线 -- AuthDevice对象对应 + QMultiMap> m_deviceMap; +- QTimer m_timer; ++ QTimer m_ReCreateTimer; + + // 设备信息-重试次数 + QMap m_retreyCreateDeviceMap; +diff --git a/src/config-helper.cpp b/src/config-helper.cpp +index 31287e4..8fc5a47 100644 +--- a/src/config-helper.cpp ++++ b/src/config-helper.cpp +@@ -16,6 +16,7 @@ + #include + #include + #include "auth-enum.h" ++#include "config.h" + + namespace Kiran + { +@@ -27,25 +28,46 @@ DeviceConf ConfigHelper::getDeviceConf(const QString &vid, const QString &pid) + Q_FOREACH (auto deviceConf, deviceConfList) + { + confSettings.beginGroup(deviceConf); +- if ((confSettings.value("Vid").toString() == vid) && +- (confSettings.value("Pid").toString() == pid)) ++ ++ QStringList idList = confSettings.value("Id").toStringList(); ++ for (const QString &id : idList) + { +- DeviceConf conf; +- conf.deviceName = confSettings.value("Name").toString(); +- conf.type = confSettings.value("Type").toInt(); +- conf.vid = confSettings.value("Vid").toString(); +- conf.pid = confSettings.value("Pid").toString(); +- conf.driver = confSettings.value("Driver").toString(); +- conf.libPath = confSettings.value("LibPath").toString(); +- +- return conf; ++ if (id == QString("%1:%2").arg(vid).arg(pid)) ++ { ++ DeviceConf conf; ++ conf.deviceName = confSettings.value("Name").toString(); ++ conf.type = confSettings.value("Type").toInt(); ++ conf.vid = vid; ++ conf.pid = pid; ++ conf.driver = confSettings.value("Driver").toString(); ++ return conf; ++ } + } ++ + confSettings.endGroup(); + } + + return DeviceConf(); + } + ++DriverConf ConfigHelper::getDriverConf(const QString &vid, const QString &pid) ++{ ++ QString driverName = getDeviceConf(vid, pid).driver; ++ ++ DriverConf driverConf; ++ QSettings confSettings(DRIVER_CONF, QSettings::NativeFormat); ++ confSettings.beginGroup(driverName); ++ ++ driverConf.driverName = driverName; ++ driverConf.enable = confSettings.value("Enable").toBool(); ++ driverConf.type = confSettings.value("Type").toInt(); ++ driverConf.libPath = confSettings.value("LibPath").toString(); ++ ++ confSettings.endGroup(); ++ ++ return driverConf; ++} ++ + QString ConfigHelper::getDriverName(const QString &vid, const QString &pid) + { + DeviceConf conf = getDeviceConf(vid, pid); +@@ -60,8 +82,8 @@ QString ConfigHelper::getDeviceName(const QString &vid, const QString &pid) + + QString ConfigHelper::getLibPath(const QString &vid, const QString &pid) + { +- DeviceConf conf = getDeviceConf(vid, pid); +- return conf.libPath; ++ DriverConf driverConf = getDriverConf(vid, pid); ++ return driverConf.libPath; + } + + int ConfigHelper::getDeviceType(const QString &vid, const QString &pid) +@@ -91,16 +113,14 @@ bool ConfigHelper::driverEnabled(const QString &vid, const QString &pid) + return enable; + } + +-void ConfigHelper::setDriverEnabled(const QString& driverName, bool enable) ++void ConfigHelper::setDriverEnabled(const QString &driverName, bool enable) + { + QSettings confSettings(DRIVER_CONF, QSettings::NativeFormat); + QStringList driverList = confSettings.childGroups(); +- QString enableStr; + if (driverList.contains(driverName)) + { +- enableStr = enable ? QString("true") : QString("false"); +- confSettings.setValue(QString("%1/Enable").arg(driverName), QVariant(enableStr)); +- KLOG_INFO() << QString("driver: %1 %2").arg(driverName).arg((enable == true) ? "enable":"disable"); ++ confSettings.setValue(QString("%1/Enable").arg(driverName), QVariant(enable)); ++ KLOG_INFO() << QString("driver: %1 %2").arg(driverName).arg(enable ? "enable" : "disable"); + } + } + +@@ -112,11 +132,16 @@ bool ConfigHelper::isDeviceSupported(const QString &vid, const QString &pid) + Q_FOREACH (auto deviceConf, deviceConfList) + { + confSettings.beginGroup(deviceConf); +- if ((confSettings.value("Vid").toString() == vid) && +- (confSettings.value("Pid").toString() == pid)) ++ ++ QStringList idList = confSettings.value("Id").toStringList(); ++ for (const QString &id : idList) + { +- return true; ++ if (id == QString("%1:%2").arg(vid).arg(pid)) ++ { ++ return true; ++ } + } ++ + confSettings.endGroup(); + } + return false; +diff --git a/src/config-helper.h b/src/config-helper.h +index 1d0d677..91cf895 100644 +--- a/src/config-helper.h ++++ b/src/config-helper.h +@@ -12,7 +12,7 @@ + * Author: luoqing + */ + #pragma once +-#include ++#include + + namespace Kiran + { +@@ -24,16 +24,25 @@ struct DeviceConf + QString vid; + QString pid; + QString driver; ++}; ++ ++struct DriverConf ++{ ++ QString driverName; ++ bool enable; ++ int type; + QString libPath; + }; + +-class ConfigHelper : public QObject ++class ConfigHelper + { + public: +- ConfigHelper(QObject *parent = nullptr) : QObject(parent){}; ++ ConfigHelper() {}; + ~ConfigHelper(){}; + + static DeviceConf getDeviceConf(const QString &vid, const QString &pid); ++ static DriverConf getDriverConf(const QString &vid, const QString &pid); ++ + static QString getDriverName(const QString &vid, const QString &pid); + static QString getDeviceName(const QString &vid, const QString &pid); + static QString getLibPath(const QString &vid, const QString &pid); +diff --git a/src/device/auth-device.cpp b/src/device/auth-device.cpp +index 7111ff6..aef4b85 100644 +--- a/src/device/auth-device.cpp ++++ b/src/device/auth-device.cpp +@@ -18,11 +18,11 @@ + #include + #include + #include "auth_device_adaptor.h" ++#include "config-helper.h" + #include "feature-db.h" + #include "kiran-auth-device-i.h" + #include "polkit-proxy.h" + #include "utils.h" +-#include "config-helper.h" + + namespace Kiran + { +@@ -38,7 +38,7 @@ AuthDevice::~AuthDevice(){}; + + bool AuthDevice::init() + { +- if(!initDevice()) ++ if (!initDevice()) + { + return false; + } +@@ -96,15 +96,6 @@ void AuthDevice::onNameLost(const QString& serviceName) + } + } + +-void AuthDevice::clearWatchedServices() +-{ +- QStringList watchedServices = m_serviceWatcher->watchedServices(); +- Q_FOREACH (auto service, watchedServices) +- { +- m_serviceWatcher->removeWatchedService(service); +- } +-} +- + DeviceInfo AuthDevice::deviceInfo() + { + DeviceInfo deviceInfo; +@@ -120,6 +111,55 @@ void AuthDevice::setDeviceInfo(const QString& idVendor, const QString& idProduct + m_idProduct = idProduct; + } + ++void AuthDevice::clearWatchedServices() ++{ ++ QStringList watchedServices = m_serviceWatcher->watchedServices(); ++ Q_FOREACH (auto service, watchedServices) ++ { ++ m_serviceWatcher->removeWatchedService(service); ++ } ++} ++ ++void AuthDevice::internalStopEnroll() ++{ ++ if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL) ++ { ++ deviceStopEnroll(); ++ setDeviceStatus(DEVICE_STATUS_IDLE); ++ clearWatchedServices(); ++ KLOG_DEBUG() << QString("enroll stop, device type:%1,name:%2 ,id:%3") ++ .arg(deviceType()) ++ .arg(deviceName()) ++ .arg(deviceID()); ++ } ++} ++ ++void AuthDevice::internalStopIdentify() ++{ ++ if (deviceStatus() == DEVICE_STATUS_DOING_IDENTIFY) ++ { ++ deviceStopIdentify(); ++ m_featuresThatNeedToIdentify.clear(); ++ setDeviceStatus(DEVICE_STATUS_IDLE); ++ clearWatchedServices(); ++ KLOG_DEBUG() << QString("identify stop, device type:%1,name:%2 ,id:%3") ++ .arg(deviceType()) ++ .arg(deviceName()) ++ .arg(deviceID()); ++ } ++} ++ ++QList AuthDevice::findFeaturesByFeatureIDs(const QStringList& featureIDs) ++{ ++ QList saveList; ++ Q_FOREACH (auto id, featureIDs) ++ { ++ QByteArray feature = FeatureDB::getInstance()->getFeature(id); ++ saveList << feature; ++ } ++ return saveList; ++} ++ + void AuthDevice::onEnrollStart(const QDBusMessage& dbusMessage, const QString& extraInfo) + { + QString message; +@@ -147,25 +187,41 @@ void AuthDevice::onEnrollStop(const QDBusMessage& dbusMessage) + + void AuthDevice::onIdentifyStart(const QDBusMessage& dbusMessage, const QString& value) + { +- QString message; + if (deviceStatus() != DEVICE_STATUS_IDLE) + { +- message = tr("Device Busy"); ++ QString message = tr("Device Busy"); + Q_EMIT m_dbusAdaptor->IdentifyStatus("", IDENTIFY_STATUS_NOT_MATCH, message); +- KLOG_DEBUG() << QString("%1, deviceID:%2").arg(message).arg(deviceID()); ++ KLOG_DEBUG() << QString("%1, deviceID:%2").arg("Device Busy").arg(deviceID()); + return; + } + ++ QStringList featureIDs; + QJsonArray jsonArray = Utils::getValueFromJsonString(value, AUTH_DEVICE_JSON_KEY_FEATURE_IDS).toArray(); + if (!jsonArray.isEmpty()) + { + QVariantList varList = jsonArray.toVariantList(); + Q_FOREACH (auto var, varList) + { +- m_identifyIDs << var.toString(); ++ featureIDs << var.toString(); + } + } + ++ if (featureIDs.isEmpty()) ++ { ++ m_featuresThatNeedToIdentify = FeatureDB::getInstance()->getFeatures(deviceInfo().idVendor, deviceInfo().idProduct, deviceType(), deviceSerialNumber()); ++ } ++ else ++ { ++ m_featuresThatNeedToIdentify = findFeaturesByFeatureIDs(featureIDs); ++ } ++ ++ if (m_featuresThatNeedToIdentify.count() == 0) ++ { ++ KLOG_DEBUG() << "no found feature id"; ++ Q_EMIT m_dbusAdaptor->IdentifyStatus("", IDENTIFY_STATUS_NOT_MATCH, tr("identify fail!")); ++ return; ++ } ++ + setDeviceStatus(DEVICE_STATUS_DOING_IDENTIFY); + m_serviceWatcher->addWatchedService(dbusMessage.service()); + auto replyMessage = dbusMessage.createReply(); +diff --git a/src/device/auth-device.h b/src/device/auth-device.h +index 581998e..7209781 100644 +--- a/src/device/auth-device.h ++++ b/src/device/auth-device.h +@@ -23,13 +23,13 @@ + #include "auth-enum.h" + #include "driver/driver.h" + #include "kiran-auth-device-i.h" ++#include "device-creator.h" + + class AuthDeviceAdaptor; + + namespace Kiran + { + typedef void *Handle; +-class BDriver; + + class AuthDevice : public QObject, protected QDBusContext + { +@@ -43,10 +43,8 @@ public: + explicit AuthDevice(const QString &vid, const QString &pid, DriverPtr driver, QObject *parent = nullptr); + virtual ~AuthDevice(); + bool init(); +- virtual bool initDevice() = 0; + + QDBusObjectPath getObjectPath() { return m_objectPath; }; +- void setDeviceType(DeviceType deviceType) { m_deviceType = deviceType; }; + + DeviceType deviceType() { return m_deviceType; }; + DeviceStatus deviceStatus() { return m_deviceStatus; }; +@@ -57,6 +55,10 @@ public: + + QString driverName() { return m_driverName; }; + ++private: ++ virtual bool initDevice() = 0; ++ friend AuthDeviceList DeviceCereator::createDevices(const QString &vid, const QString &pid, DriverPtr driver); ++ + public Q_SLOTS: + virtual void EnrollStart(const QString &extraInfo); + virtual void EnrollStop(); +@@ -65,6 +67,7 @@ public Q_SLOTS: + virtual QStringList GetFeatureIDList(); + + protected: ++ void setDeviceType(DeviceType deviceType) { m_deviceType = deviceType; }; + void setDeviceStatus(DeviceStatus deviceStatus) { m_deviceStatus = deviceStatus; }; + void setDeviceName(const QString &deviceName) { m_deviceName = deviceName; }; + void setDeviceInfo(const QString &idVendor, const QString &idProduct); +@@ -73,8 +76,13 @@ protected: + void setDriverName(const QString &driverName) { m_driverName = driverName; }; + + void clearWatchedServices(); +- virtual void internalStopEnroll() = 0; +- virtual void internalStopIdentify() = 0; ++ void internalStopEnroll(); ++ void internalStopIdentify(); ++ ++ virtual void deviceStopEnroll() = 0; ++ virtual void deviceStopIdentify() = 0; ++ ++ QList getFeaturesThatNeedToIdentify() {return m_featuresThatNeedToIdentify;}; + + private: + void onEnrollStart(const QDBusMessage &message, const QString &extraInfo); +@@ -85,21 +93,23 @@ private: + virtual void doingEnrollStart(const QString &extraInfo) = 0; + virtual void doingIdentifyStart(const QString &value) = 0; + +-private Q_SLOTS: +- void onNameLost(const QString &serviceName); +- +-private: + void registerDBusObject(); + void initServiceWatcher(); + ++ QList findFeaturesByFeatureIDs(const QStringList &featureIDs); ++ ++private Q_SLOTS: ++ void onNameLost(const QString &serviceName); ++ + Q_SIGNALS: + void retry(); + + protected: + QSharedPointer m_dbusAdaptor; +- QStringList m_identifyIDs; + + private: ++ QList m_featuresThatNeedToIdentify; ++ + QString m_driverName; + QString m_deviceID; + +diff --git a/src/device/bio-device.cpp b/src/device/bio-device.cpp +index 8d73cd5..841d48c 100644 +--- a/src/device/bio-device.cpp ++++ b/src/device/bio-device.cpp +@@ -22,8 +22,8 @@ namespace Kiran + { + #define TEMPLATE_MAX_NUMBER 1000 + +-BioDevice::BioDevice(const QString &vid, const QString &pid, DriverPtr driver,QObject *parent) : AuthDevice{vid,pid,driver,parent}, +- m_futureWatcher(nullptr) ++BioDevice::BioDevice(const QString &vid, const QString &pid, DriverPtr driver, QObject *parent) : AuthDevice{vid, pid, driver, parent}, ++ m_futureWatcher(nullptr) + { + initFutureWatcher(); + } +@@ -36,7 +36,7 @@ void BioDevice::doingEnrollStart(const QString &extraInfo) + { + KLOG_DEBUG() << "biological information enroll start"; + // 获取当前保存的特征模板,判断是否达到最大数目 +- QByteArrayList saveList = FeatureDB::getInstance()->getFeatures(deviceInfo().idVendor, deviceInfo().idProduct, deviceType(),deviceSerialNumber()); ++ QByteArrayList saveList = FeatureDB::getInstance()->getFeatures(deviceInfo().idVendor, deviceInfo().idProduct, deviceType(), deviceSerialNumber()); + if (saveList.count() == TEMPLATE_MAX_NUMBER) + { + QString message = tr("feature has reached the upper limit of %1").arg(TEMPLATE_MAX_NUMBER); +@@ -87,30 +87,6 @@ void BioDevice::doingIdentifyStart(const QString &value) + m_dbusAdaptor->IdentifyStatus("", ENROLL_STATUS_NORMAL, message); + } + +-void BioDevice::internalStopEnroll() +-{ +- if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL) +- { +- acquireFeatureStop(); +- m_enrollTemplates.clear(); +- setDeviceStatus(DEVICE_STATUS_IDLE); +- clearWatchedServices(); +- KLOG_DEBUG() << QString("device type:%1,internal enroll stop").arg(deviceType()); +- } +-} +- +-void BioDevice::internalStopIdentify() +-{ +- if (deviceStatus() == DEVICE_STATUS_DOING_IDENTIFY) +- { +- acquireFeatureStop(); +- m_identifyIDs.clear(); +- setDeviceStatus(DEVICE_STATUS_IDLE); +- clearWatchedServices(); +- KLOG_DEBUG() << QString("device type:%1,internal identify stop").arg(deviceType()); +- } +-} +- + void BioDevice::doingEnrollProcess(QByteArray feature) + { + int templatesCount = enrollTemplatesFromCache().count(); +@@ -146,12 +122,13 @@ void BioDevice::doingEnrollProcess(QByteArray feature) + else if (enrollTemplatesFromCache().count() == mergeTemplateCount()) + { + enrollTemplateMerge(); ++ internalStopEnroll(); + } + } + + void BioDevice::doingIdentifyProcess(QByteArray feature) + { +- QString featureID = identifyFeature(feature, m_identifyIDs); ++ QString featureID = identifyFeature(feature, getFeaturesThatNeedToIdentify()); + if (!featureID.isEmpty()) + { + notifyIdentifyProcess(IDENTIFY_PROCESS_MACTCH, featureID); +@@ -208,6 +185,17 @@ void BioDevice::saveEnrollTemplateToCache(QByteArray enrollTemplate) + } + } + ++void BioDevice::deviceStopEnroll() ++{ ++ acquireFeatureStop(); ++ m_enrollTemplates.clear(); ++} ++ ++void BioDevice::deviceStopIdentify() ++{ ++ acquireFeatureStop(); ++} ++ + void BioDevice::enrollProcessRetry() + { + Q_EMIT this->retry(); +@@ -215,7 +203,8 @@ void BioDevice::enrollProcessRetry() + + QString BioDevice::isFeatureEnrolled(QByteArray fpTemplate) + { +- return identifyFeature(fpTemplate, QStringList()); ++ QList features = FeatureDB::getInstance()->getFeatures(deviceInfo().idVendor, deviceInfo().idProduct, deviceType(), deviceSerialNumber()); ++ return identifyFeature(fpTemplate, features); + } + + void BioDevice::initFutureWatcher() +diff --git a/src/device/bio-device.h b/src/device/bio-device.h +index 643acf1..4c05923 100644 +--- a/src/device/bio-device.h ++++ b/src/device/bio-device.h +@@ -20,7 +20,7 @@ class BioDevice : public AuthDevice + { + Q_OBJECT + public: +- explicit BioDevice(const QString &vid, const QString &pid, DriverPtr driver,QObject *parent = nullptr); ++ explicit BioDevice(const QString &vid, const QString &pid, DriverPtr driver, QObject *parent = nullptr); + ~BioDevice(); + int mergeTemplateCount() { return m_mergeTemplateCount; }; + void setMergeTemplateCount(int count) { m_mergeTemplateCount = count; }; +@@ -29,16 +29,16 @@ protected: + virtual QByteArray acquireFeature() = 0; + virtual void acquireFeatureStop() = 0; + virtual void acquireFeatureFail() = 0; +- virtual QString identifyFeature(QByteArray feature, QStringList featureIDs) = 0; +- +- virtual void enrollTemplateMerge() {}; +- virtual int enrollTemplateMatch(QByteArray fpTemplate1, QByteArray fpTemplate2) {return GENERAL_RESULT_OK;}; +- +- void internalStopEnroll() override; +- void internalStopIdentify() override; ++ virtual QString identifyFeature(QByteArray feature, QList existedfeatures) = 0; ++ ++ virtual void enrollTemplateMerge(){}; ++ virtual int enrollTemplateMatch(QByteArray fpTemplate1, QByteArray fpTemplate2) { return GENERAL_RESULT_OK; }; ++ ++ virtual void deviceStopEnroll() override; ++ virtual void deviceStopIdentify() override; + + virtual void enrollProcessRetry(); +- //TODO:优化通知 ++ // TODO:优化通知 + virtual void notifyEnrollProcess(EnrollProcess process, const QString &featureID = QString()) = 0; + virtual void notifyIdentifyProcess(IdentifyProcess process, const QString &featureID = QString()) = 0; + +diff --git a/src/device/device-creator.cpp b/src/device/device-creator.cpp +index d7cd926..1fb760f 100644 +--- a/src/device/device-creator.cpp ++++ b/src/device/device-creator.cpp +@@ -45,7 +45,7 @@ DeviceCereator::~DeviceCereator() + { + } + +-AuthDeviceList DeviceCereator::getDevices(const QString &vid, const QString &pid, DriverPtr driver) ++AuthDeviceList DeviceCereator::createDevices(const QString &vid, const QString &pid, DriverPtr driver) + { + AuthDeviceList deviceList; + QStringList driverNameList = m_deviceFuncMap.keys(); +@@ -72,12 +72,7 @@ AuthDeviceList DeviceCereator::getDevices(const QString &vid, const QString &pid + (irisDevicePtr->init())) + { + deviceList << faceDevicePtr << irisDevicePtr; +- } +- else +- { +- KLOG_ERROR() << QString("device %1:%2 init failed!").arg(vid).arg(pid); +- faceDevicePtr.clear(); +- irisDevicePtr.clear(); ++ return deviceList; + } + } + else +@@ -87,14 +82,12 @@ AuthDeviceList DeviceCereator::getDevices(const QString &vid, const QString &pid + if (devicePtr->init()) + { + deviceList << devicePtr; +- } +- else +- { +- KLOG_ERROR() << QString("device %1:%2 init failed!").arg(vid).arg(pid); +- devicePtr.clear(); ++ return deviceList; + } + } +- return deviceList; ++ ++ KLOG_ERROR() << QString("device %1:%2 init failed!").arg(vid).arg(pid); ++ return AuthDeviceList(); + } + + void DeviceCereator::registerDevice(QString driverName, std::function func) +diff --git a/src/device/device-creator.h b/src/device/device-creator.h +index be074cb..9b3db32 100644 +--- a/src/device/device-creator.h ++++ b/src/device/device-creator.h +@@ -36,7 +36,7 @@ public: + static DeviceCereator *getInstance(); + ~DeviceCereator(); + +- AuthDeviceList getDevices(const QString &vid, const QString &pid, DriverPtr driver); ++ AuthDeviceList createDevices(const QString &vid, const QString &pid, DriverPtr driver); + + void registerDevice(QString driverName, + std::function func); +diff --git a/src/device/finger-vein/fv-sd-device.cpp b/src/device/finger-vein/fv-sd-device.cpp +index 67dcab8..442f227 100644 +--- a/src/device/finger-vein/fv-sd-device.cpp ++++ b/src/device/finger-vein/fv-sd-device.cpp +@@ -240,42 +240,21 @@ void FVSDDevice::enrollTemplateMerge() + KLOG_DEBUG() << "Finger vein template fusion failed:" << ret; + notifyEnrollProcess(ENROLL_PROCESS_MEGER_FAIL); + } +- internalStopEnroll(); + } + + QString FVSDDevice::isFeatureEnrolled(QByteArray fpTemplate) + { + QByteArray featureForVerify = getFeatureFromImage(fpTemplate, EXTRACT_FEATURE_VERIFY); +- QString featureID = identifyFeature(featureForVerify, QStringList()); ++ QList features = FeatureDB::getInstance()->getFeatures(deviceInfo().idVendor, deviceInfo().idProduct, deviceType(), deviceSerialNumber()); ++ QString featureID = identifyFeature(featureForVerify, features); + return featureID; + } + +-QString FVSDDevice::identifyFeature(QByteArray feature, QStringList featureIDs) ++QString FVSDDevice::identifyFeature(QByteArray feature, QList existedfeatures) + { +- QList saveList; + QString featureID; +- DeviceInfo deviceInfo = this->deviceInfo(); +- if (featureIDs.isEmpty()) +- { +- saveList = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct, deviceType(), deviceSerialNumber()); +- } +- else +- { +- Q_FOREACH (auto id, featureIDs) +- { +- QByteArray feature = FeatureDB::getInstance()->getFeature(id); +- if (!feature.isEmpty()) +- saveList << feature; +- } +- } +- +- if (saveList.count() == 0) +- { +- return QString(); +- } +- + QByteArray saveTempl; +- Q_FOREACH (auto saveFeature, saveList) ++ Q_FOREACH (auto saveFeature, existedfeatures) + { + saveTempl.append(saveFeature); + } +@@ -283,11 +262,10 @@ QString FVSDDevice::identifyFeature(QByteArray feature, QStringList featureIDs) + int matchIndex = 0; + int matchScore = 0; + unsigned char updateTmpl[TEMPLATE_SIZE] = {0}; // 自我学习后的新模板 +- KLOG_DEBUG() << "saveList.count():" << saveList.count(); + + int matchResult = m_driver->TGFeatureMatchTmpl1N((unsigned char *)feature.data(), + (unsigned char *)saveTempl.data(), +- saveList.count(), ++ existedfeatures.count(), + &matchIndex, + &matchScore, + updateTmpl); +diff --git a/src/device/finger-vein/fv-sd-device.h b/src/device/finger-vein/fv-sd-device.h +index 0a4f5a4..0a82055 100644 +--- a/src/device/finger-vein/fv-sd-device.h ++++ b/src/device/finger-vein/fv-sd-device.h +@@ -33,9 +33,9 @@ class FVSDDevice : public BioDevice + public: + explicit FVSDDevice(const QString &vid, const QString &pid, DriverPtr driver,QObject *parent = nullptr); + ~FVSDDevice(); +- bool initDevice() override; + + private: ++ bool initDevice() override; + QByteArray acquireFeature() override; + void acquireFeatureStop() override; + void acquireFeatureFail() override; +@@ -46,7 +46,7 @@ private: + void enrollProcessRetry() override; + + QString isFeatureEnrolled(QByteArray fpTemplate) override; +- QString identifyFeature(QByteArray feature, QStringList featureIDs) override; ++ QString identifyFeature(QByteArray feature, QList existedfeatures) override; + + void notifyEnrollProcess(EnrollProcess process, const QString &featureID = QString()) override; + void notifyIdentifyProcess(IdentifyProcess process, const QString &featureID = QString()) override; +diff --git a/src/device/fingerprint/fp-zk-device.cpp b/src/device/fingerprint/fp-zk-device.cpp +index 3a26cf5..3a818f3 100644 +--- a/src/device/fingerprint/fp-zk-device.cpp ++++ b/src/device/fingerprint/fp-zk-device.cpp +@@ -233,34 +233,12 @@ int FPZKDevice::enrollTemplateMatch(QByteArray fpTemplate1, QByteArray fpTemplat + return score > 0 ? GENERAL_RESULT_OK : GENERAL_RESULT_FAIL; + } + +-QString FPZKDevice::identifyFeature(QByteArray fpTemplate, QStringList featureIDs) ++QString FPZKDevice::identifyFeature(QByteArray fpTemplate, QList existedfeatures) + { +- QList saveList; + QString featureID; +- DeviceInfo info = this->deviceInfo(); +- if (featureIDs.isEmpty()) ++ for (int j = 0; j < existedfeatures.count(); j++) + { +- saveList = FeatureDB::getInstance()->getFeatures(info.idVendor, info.idProduct, deviceType(), deviceSerialNumber()); +- } +- else +- { +- Q_FOREACH (auto id, featureIDs) +- { +- QByteArray feature = FeatureDB::getInstance()->getFeature(id); +- if (!feature.isEmpty()) +- saveList << feature; +- } +- } +- +- if (saveList.count() == 0) +- { +- KLOG_DEBUG() << "no found feature"; +- return QString(); +- } +- +- for (int j = 0; j < saveList.count(); j++) +- { +- auto saveTemplate = saveList.value(j); ++ auto saveTemplate = existedfeatures.value(j); + int ret = enrollTemplateMatch(fpTemplate, saveTemplate); + // 指纹已经存在,直接返回该指纹 + if (ret == GENERAL_RESULT_OK) +@@ -294,7 +272,6 @@ void FPZKDevice::enrollTemplateMerge() + { + // 三个模板merge失败,判定为录入失败,需要重新录入 + notifyEnrollProcess(ENROLL_PROCESS_MEGER_FAIL); +- internalStopEnroll(); + return; + } + +@@ -318,7 +295,6 @@ void FPZKDevice::enrollTemplateMerge() + // 如果合成后的指纹与先前录入的指纹不匹配,判定为录入失败,需要重新录入 + notifyEnrollProcess(ENROLL_PROCESS_INCONSISTENT_FEATURE_AFTER_MERGED); + } +- internalStopEnroll(); + } + + void FPZKDevice::notifyEnrollProcess(EnrollProcess process, const QString& featureID) +diff --git a/src/device/fingerprint/fp-zk-device.h b/src/device/fingerprint/fp-zk-device.h +index 083a6b8..d9a6b2c 100644 +--- a/src/device/fingerprint/fp-zk-device.h ++++ b/src/device/fingerprint/fp-zk-device.h +@@ -30,9 +30,9 @@ public: + explicit FPZKDevice(const QString &vid, const QString &pid, DriverPtr driver,QObject* parent = nullptr); + ~FPZKDevice(); + +- bool initDevice() override; + + private: ++ bool initDevice() override; + QByteArray acquireFeature() override; + // 停止采集指纹模板 + void acquireFeatureStop() override; +@@ -52,7 +52,7 @@ private: + // 对比两枚指纹是否匹配 + int enrollTemplateMatch(QByteArray fpTemplate1, QByteArray fpTemplate2) override; + +- QString identifyFeature(QByteArray fpTemplate, QStringList featureIDs) override; ++ QString identifyFeature(QByteArray fpTemplate, QList existedfeatures) override; + + bool saveFPrintTemplate(QByteArray fpTemplate, const QString& featureID); + +diff --git a/src/device/multi-function/mf-iristar-device.cpp b/src/device/multi-function/mf-iristar-device.cpp +index 7a0ce84..27e1809 100644 +--- a/src/device/multi-function/mf-iristar-device.cpp ++++ b/src/device/multi-function/mf-iristar-device.cpp +@@ -19,6 +19,7 @@ + #include "utils.h" + #include "device/device-creator.h" + #include "config-helper.h" ++#include "feature-db.h" + + namespace Kiran + { +@@ -29,8 +30,6 @@ REGISTER_DEVICE(IRISTAR_DRIVER_NAME,MFIriStarDevice); + MFIriStarDevice::MFIriStarDevice(const QString &vid, const QString &pid, DriverPtr driver, QObject *parent) : + AuthDevice(vid, pid, driver, parent) + { +- setDeviceName(ConfigHelper::getDriverName(vid,pid)); +- + m_driver = driver.dynamicCast(); + m_driver->ref(); + m_driver->setDeviceInfo(vid,pid); +@@ -63,8 +62,10 @@ bool MFIriStarDevice::initDevice() + } + + void MFIriStarDevice::doingEnrollStart(const QString &extraInfo) +-{ +- m_driver->doingEnrollStart(deviceType()); ++{ ++ QList features = FeatureDB::getInstance()->getFeatures(deviceInfo().idVendor, deviceInfo().idProduct, deviceType(), deviceSerialNumber()); ++ // 注册前需要传入已经存在的特征,判断之前是否注册过 ++ m_driver->doingEnrollStart(deviceType(),features); + QString message; + if (deviceType() == DEVICE_TYPE_Iris) + { +@@ -79,7 +80,7 @@ void MFIriStarDevice::doingEnrollStart(const QString &extraInfo) + + void MFIriStarDevice::doingIdentifyStart(const QString &value) + { +- m_driver->doingIdentifyStart(deviceType(), m_identifyIDs); ++ m_driver->doingIdentifyStart(deviceType(), getFeaturesThatNeedToIdentify()); + QString message; + if (deviceType() == DEVICE_TYPE_Iris) + { +@@ -92,27 +93,14 @@ void MFIriStarDevice::doingIdentifyStart(const QString &value) + m_dbusAdaptor->IdentifyStatus("", ENROLL_STATUS_NORMAL, message); + } + +-void MFIriStarDevice::internalStopEnroll() ++void MFIriStarDevice::deviceStopEnroll() + { +- if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL) +- { +- m_driver->stop(); +- setDeviceStatus(DEVICE_STATUS_IDLE); +- clearWatchedServices(); +- KLOG_DEBUG() << "stop Enroll"; +- } ++ m_driver->stop(); + } + +-void MFIriStarDevice::internalStopIdentify() ++void MFIriStarDevice::deviceStopIdentify() + { +- if (deviceStatus() == DEVICE_STATUS_DOING_IDENTIFY) +- { +- m_driver->stop(); +- m_identifyIDs.clear(); +- setDeviceStatus(DEVICE_STATUS_IDLE); +- clearWatchedServices(); +- KLOG_DEBUG() << "stop Identify"; +- } ++ m_driver->stop(); + } + + void MFIriStarDevice::onEnrollProcess(EnrollProcess process, DeviceType type, const QString &featureID) +diff --git a/src/device/multi-function/mf-iristar-device.h b/src/device/multi-function/mf-iristar-device.h +index c204607..4e04b1c 100644 +--- a/src/device/multi-function/mf-iristar-device.h ++++ b/src/device/multi-function/mf-iristar-device.h +@@ -26,13 +26,12 @@ public: + explicit MFIriStarDevice(const QString &vid, const QString &pid, DriverPtr driver, QObject *parent = nullptr); + ~MFIriStarDevice(); + +- bool initDevice() override; +- + private: ++ bool initDevice() override; + void doingEnrollStart(const QString &extraInfo) override; + void doingIdentifyStart(const QString &value) override; +- void internalStopEnroll() override; +- void internalStopIdentify() override; ++ void deviceStopEnroll() override; ++ void deviceStopIdentify() override; + + void notifyEnrollProcess(EnrollProcess process, const QString &featureID = QString()); + void notifyIdentifyProcess(IdentifyProcess process, const QString &featureID = QString()); +diff --git a/src/device/ukey/ukey-skf-device.cpp b/src/device/ukey/ukey-skf-device.cpp +index 2d7f03d..82bd13d 100644 +--- a/src/device/ukey/ukey-skf-device.cpp ++++ b/src/device/ukey/ukey-skf-device.cpp +@@ -16,27 +16,27 @@ + #include + #include + #include ++#include + #include "auth-enum.h" + #include "auth_device_adaptor.h" ++#include "config-helper.h" ++#include "device/device-creator.h" + #include "feature-db.h" + #include "utils.h" +-#include "device/device-creator.h" +-#include +-#include "config-helper.h" + + namespace Kiran + { +-REGISTER_DEVICE(UKEY_SKF_DRIVER_NAME,UKeySKFDevice); ++REGISTER_DEVICE(UKEY_SKF_DRIVER_NAME, UKeySKFDevice); + + QStringList UKeySKFDevice::m_existingSerialNumber; + + UKeySKFDevice::UKeySKFDevice(const QString &vid, const QString &pid, DriverPtr driver, QObject *parent) : AuthDevice{vid, pid, driver, parent} + { +- //NOTE:并未使用传递进来的DriverPtr ++ // NOTE:并未使用传递进来的DriverPtr + setDeviceType(DEVICE_TYPE_UKey); +- setDriverName(ConfigHelper::getDriverName(vid,pid)); +- m_driverLibPath = ConfigHelper::getLibPath(vid,pid); +- ++ setDriverName(ConfigHelper::getDriverName(vid, pid)); ++ m_driverLibPath = ConfigHelper::getLibPath(vid, pid); ++ + /** + * NOTE: + * UKey设备插入时,设备可能处在未准备好的状态,无法获取到serialNumber +@@ -167,7 +167,7 @@ void UKeySKFDevice::bindingUKey(DEVHANDLE devHandle, const QString &pin) + * 不用保存PublicKey和systemUser的关系,目前只有一个用户 + */ + QByteArray keyFeature; +- keyFeature.append((char *)&publicKey,sizeof(publicKey)); ++ keyFeature.append((char *)&publicKey, sizeof(publicKey)); + KLOG_DEBUG() << "keyFeature:" << keyFeature; + + QString featureID = QCryptographicHash::hash(keyFeature, QCryptographicHash::Md5).toHex(); +@@ -248,30 +248,7 @@ void UKeySKFDevice::doingIdentifyStart(const QString &value) + { + QString message = tr("The pin code cannot be empty!"); + Q_EMIT m_dbusAdaptor->IdentifyStatus("", IDENTIFY_STATUS_NOT_MATCH, message); +- KLOG_ERROR() << message; +- internalStopIdentify(); +- return; +- } +- +- QList saveList; +- DeviceInfo deviceInfo = this->deviceInfo(); +- if (m_identifyIDs.isEmpty()) +- { +- saveList = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct, deviceType(), deviceSerialNumber()); +- } +- else +- { +- Q_FOREACH (auto id, m_identifyIDs) +- { +- QByteArray feature = FeatureDB::getInstance()->getFeature(id); +- saveList << feature; +- } +- } +- +- if (saveList.count() == 0) +- { +- KLOG_DEBUG() << "no found feature id"; +- notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH); ++ KLOG_ERROR() << "The pin code cannot be empty!"; + internalStopIdentify(); + return; + } +@@ -284,7 +261,8 @@ void UKeySKFDevice::doingIdentifyStart(const QString &value) + internalStopIdentify(); + return; + } +- ++ ++ QList saveList = getFeaturesThatNeedToIdentify(); + for (int j = 0; j < saveList.count(); j++) + { + auto savedKey = saveList.value(j); +@@ -294,35 +272,22 @@ void UKeySKFDevice::doingIdentifyStart(const QString &value) + internalStopIdentify(); + } + +-void UKeySKFDevice::internalStopEnroll() ++void UKeySKFDevice::deviceStopEnroll() + { +- if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL) ++ if (m_driver) + { +- setDeviceStatus(DEVICE_STATUS_IDLE); +- clearWatchedServices(); +- if (m_driver) +- { +- KLOG_DEBUG() << "delete driver"; +- delete m_driver; +- m_driver = nullptr; +- } +- KLOG_DEBUG() << "stop Enroll"; ++ KLOG_DEBUG() << "delete driver"; ++ delete m_driver; ++ m_driver = nullptr; + } + } + +-void UKeySKFDevice::internalStopIdentify() ++void UKeySKFDevice::deviceStopIdentify() + { +- if (deviceStatus() == DEVICE_STATUS_DOING_IDENTIFY) ++ if (m_driver) + { +- m_identifyIDs.clear(); +- setDeviceStatus(DEVICE_STATUS_IDLE); +- clearWatchedServices(); +- if (m_driver) +- { +- delete m_driver; +- m_driver = nullptr; +- } +- KLOG_DEBUG() << "stopIdentify"; ++ delete m_driver; ++ m_driver = nullptr; + } + } + +diff --git a/src/device/ukey/ukey-skf-device.h b/src/device/ukey/ukey-skf-device.h +index 73d1092..b9628ca 100644 +--- a/src/device/ukey/ukey-skf-device.h ++++ b/src/device/ukey/ukey-skf-device.h +@@ -29,18 +29,18 @@ public: + explicit UKeySKFDevice(const QString &vid, const QString &pid, DriverPtr driver, QObject *parent = nullptr); + ~UKeySKFDevice(); + +- bool initDevice() override; + void resetUkey(); + + private Q_SLOTS: + bool initSerialNumber(); + + private: ++ bool initDevice() override; + void doingEnrollStart(const QString &extraInfo) override; + void doingIdentifyStart(const QString &value) override; + +- void internalStopEnroll() override; +- void internalStopIdentify() override; ++ void deviceStopEnroll() override; ++ void deviceStopIdentify() override; + + void identifyKeyFeature(const QString &pin, QByteArray keyFeature); + +diff --git a/src/driver/multi-function/mf-iristar-driver.cpp b/src/driver/multi-function/mf-iristar-driver.cpp +index b2153a6..7c11d43 100644 +--- a/src/driver/multi-function/mf-iristar-driver.cpp ++++ b/src/driver/multi-function/mf-iristar-driver.cpp +@@ -264,11 +264,13 @@ void MFIriStarDriver::reset() + } + + /** ++ * 开启注册流程,用于获取人脸/虹膜注册特征信息和图像,非阻塞调用。结果数据通过结果回调函数irsResultCallback返回 ++ * 采集数据的特征类型,只支持I/F,其中I表示虹膜,F表示人脸 + * 注册模式:F-人脸 I-双眼 l-单左眼 r-单右眼 + * 识别模式:F-人脸 I-双眼 + * 识别虹膜时不区分单双眼,只传双眼即可;只有W200设备支持注册时选择单双眼,其他设备全部是双眼 + */ +-void MFIriStarDriver::doingEnrollStart(DeviceType deviceType) ++void MFIriStarDriver::doingEnrollStart(DeviceType deviceType, QList existedfeatures) + { + if (deviceType == DEVICE_TYPE_Iris) + { +@@ -283,12 +285,22 @@ void MFIriStarDriver::doingEnrollStart(DeviceType deviceType) + setVideoStream(m_algorithmType.c_str()); + setDeviceStatus(DEVICE_STATUS_DOING_ENROLL); + +- // 开启注册流程,用于获取人脸/虹膜注册特征信息和图像,非阻塞调用。结果数据通过结果回调函数irsResultCallback返回 +- // 采集数据的特征类型,只支持I/F,其中I表示虹膜,F表示人脸 +- int retVal = prepareEnroll(m_algorithmType.c_str()); ++ // 已经存在的特征为空,说明未录入过 ++ if (existedfeatures.count() == 0) ++ { ++ Q_EMIT addFeature(); ++ return; ++ } ++ // 注册前需要开启识别流程判断之前是否注册过 ++ int retVal = startIdentify(existedfeatures); ++ if (retVal == -1) ++ { ++ Q_EMIT addFeature(); ++ return; ++ } + } + +-void MFIriStarDriver::doingIdentifyStart(DeviceType deviceType, QStringList featureIDs) ++void MFIriStarDriver::doingIdentifyStart(DeviceType deviceType, QList features) + { + if (deviceType == DEVICE_TYPE_Iris) + { +@@ -303,7 +315,7 @@ void MFIriStarDriver::doingIdentifyStart(DeviceType deviceType, QStringList feat + setVideoStream(m_algorithmType.c_str()); + setDeviceStatus(DEVICE_STATUS_DOING_IDENTIFY); + +- int retVal = startIdentify(featureIDs); ++ int retVal = startIdentify(features); + + if (retVal != GENERAL_RESULT_OK) + { +@@ -346,56 +358,18 @@ bool MFIriStarDriver::isLoaded() + return m_driverLib->isLoaded; + } + +-int MFIriStarDriver::prepareEnroll(const char *objectType) ++int MFIriStarDriver::startIdentify(QList features) + { +- KLOG_DEBUG() << "prepareEnroll"; +- // 注册前需要开启识别流程判断之前是否注册过 +- int retVal = startIdentify(QStringList()); +- if (retVal == -1) +- { +- KLOG_DEBUG() << "add Feature"; +- Q_EMIT addFeature(); +- } +- return retVal; +-} +- +-int MFIriStarDriver::startIdentify(QStringList featureIDs) +-{ +- KLOG_DEBUG() << "startIdentify"; +- // TODO:这段代码有多处使用,可以提炼复用 +- QList saveList; +- QString featureID; +- +- if (featureIDs.isEmpty()) +- { +- saveList = FeatureDB::getInstance()->getFeatures(m_idVendor, m_idProduct, (DeviceType)m_currentDeviceType, QString()); +- } +- else +- { +- Q_FOREACH (auto id, featureIDs) +- { +- QByteArray feature = FeatureDB::getInstance()->getFeature(id); +- if (!feature.isEmpty()) +- saveList << feature; +- } +- } +- +- if (saveList.count() == 0) +- { +- KLOG_DEBUG() << " no features in the database"; +- return -1; +- } +- + int retVal = 0; +- m_identifyFeatureCache = saveList; ++ m_identifyFeatureCache = features; + // 识别类型,只支持I/F,其中I表示虹膜,F表示人脸 + if (m_algorithmType == ALGORITHM_TYPE_IRIS) + { +- retVal = identifyIris(saveList); ++ retVal = identifyIris(features); + } + else if (m_algorithmType == ALGORITHM_TYPE_FACE) + { +- retVal = identifyFace(saveList); ++ retVal = identifyFace(features); + } + + return retVal; +@@ -480,7 +454,6 @@ void MFIriStarDriver::onStartEnroll() + } + + retVal = m_driverLib->IRS_control(m_irsHandle, IRS_CONTROL_START_ENROLL, (void *)m_algorithmType.c_str(), strlen(m_algorithmType.c_str())); +- KLOG_DEBUG() << "IRS_CONTROL_START_ENROLL:" << retVal; + if (retVal != GENERAL_RESULT_OK) + { + KLOG_ERROR() << "start enroll failed:" << retVal; +diff --git a/src/driver/multi-function/mf-iristar-driver.h b/src/driver/multi-function/mf-iristar-driver.h +index 9c7864c..f03035a 100644 +--- a/src/driver/multi-function/mf-iristar-driver.h ++++ b/src/driver/multi-function/mf-iristar-driver.h +@@ -43,8 +43,8 @@ public: + bool initDriver(const QString &libPath = QString()) override; + bool isInitialized() { return m_isInitialized; }; + +- void doingEnrollStart(DeviceType deviceType); +- void doingIdentifyStart(DeviceType deviceType, QStringList featureIDs); ++ void doingEnrollStart(DeviceType deviceType, QList existedfeatures); ++ void doingIdentifyStart(DeviceType deviceType, QList features); + + void stop(); + void setDeviceInfo(const QString &idVendor, const QString &idProduct); +@@ -76,9 +76,7 @@ private: + void handleRecognized(IRS_Results *results); + void handleRecognizingFailed(IRS_Results *results); + +- int prepareEnroll(const char *object); +- +- int startIdentify(QStringList featureIDs); ++ int startIdentify(QList features); + int identifyIris(QList features); + int identifyFace(QList features); + +diff --git a/src/feature-db.cpp b/src/feature-db.cpp +index 6cf8735..fee5aee 100644 +--- a/src/feature-db.cpp ++++ b/src/feature-db.cpp +@@ -77,7 +77,7 @@ bool FeatureDB::createDBConnection() + + if (!query.exec(createTable)) + { +- KLOG_DEBUG() << "query.lastError():" << query.lastError(); ++ KLOG_DEBUG() << "failed to create table in the database:" << query.lastError(); + } + } + return true; +diff --git a/src/utils.cpp b/src/utils.cpp +index 1735ea8..3061263 100644 +--- a/src/utils.cpp ++++ b/src/utils.cpp +@@ -18,7 +18,7 @@ + #include + #include + #include +- ++#include "config.h" + + namespace Kiran + { +@@ -101,32 +101,5 @@ QJsonValue getValueFromJsonString(const QString& json, const QString& key) + return jsonObject.value(key); + } + +-QStringList getDriverBlackList() +-{ +- QSettings confSettings(DRIVER_BLACK_LIST_CONF, QSettings::NativeFormat); +- return confSettings.value(CONF_FILE_DISABLE_DRIVER_NAME).toStringList(); +-} +- +-bool driverEnabled(const QString& driverName) +-{ +- QSettings confSettings(DRIVER_CONF, QSettings::NativeFormat); +- QVariant value = confSettings.value(QString("%1/Enable").arg(driverName)); +- if (value.isValid()) +- { +- if (value.toString() == "false") +- { +- return false; +- } +- else if (value.toString() == "true") +- { +- return true; +- } +- else +- return false; +- } +- else +- return false; +-} +- + } // namespace Utils + } // namespace Kiran +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index d80174b..1c28065 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -28,8 +28,5 @@ QString getDeviceName(const QString& idVendor, const QString& idProduct); + + QJsonValue getValueFromJsonString(const QString& json, const QString& key); + +-QStringList getDriverBlackList(); +- +-bool driverEnabled(const QString& driverName); + } // namespace Utils + } // namespace Kiran +diff --git a/ukey-manager/ukey-manager.cpp b/ukey-manager/ukey-manager.cpp +index d815d72..8a4e86e 100644 +--- a/ukey-manager/ukey-manager.cpp ++++ b/ukey-manager/ukey-manager.cpp +@@ -4,6 +4,7 @@ + #include "ukey-skf-driver.h" + #include + #include "auth-enum.h" ++#include "config.h" + + #define DEFAULT_USER_PINCODE "12345678" + +-- +2.33.0 + diff --git a/0004-fix-mf-iristar-driver-when-the-iris-face-integrated-.patch b/0004-fix-mf-iristar-driver-when-the-iris-face-integrated-.patch new file mode 100644 index 0000000..e089fe9 --- /dev/null +++ b/0004-fix-mf-iristar-driver-when-the-iris-face-integrated-.patch @@ -0,0 +1,100 @@ +From 444f3d097fa42fbfde53004ffe0404bb31313bac Mon Sep 17 00:00:00 2001 +From: luoqing +Date: Tue, 16 Jan 2024 11:57:08 +0800 +Subject: [PATCH 4/6] fix(mf-iristar-driver):when the iris face integrated + device is removed, the memory cannot be released and the process is + blocked.When the face iris device object is removed, restart the service to + release resources +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +- 虹膜人脸一体设备在拔出时无法释放内存并且会阻塞进程,当释放人脸虹膜设备对象时,如果当前设备已经被拔出了,就重启服务,释放资源。 + +Related #25243 +--- + .../multi-function/mf-iristar-driver.cpp | 41 +++++++++++++++---- + 1 file changed, 34 insertions(+), 7 deletions(-) + +diff --git a/src/driver/multi-function/mf-iristar-driver.cpp b/src/driver/multi-function/mf-iristar-driver.cpp +index 7c11d43..f65af2d 100644 +--- a/src/driver/multi-function/mf-iristar-driver.cpp ++++ b/src/driver/multi-function/mf-iristar-driver.cpp +@@ -17,12 +17,14 @@ + #include + #include + #include ++#include + #include + #include + #include "auth-enum.h" + #include "auth_device_adaptor.h" + #include "driver/driver-factory.h" + #include "feature-db.h" ++#include "utils.h" + + namespace Kiran + { +@@ -46,6 +48,7 @@ std::function Callback::func; + + #define IRIS_IS_DRIVER_LIB "libirs_sdk2.so" + #define IRIS_IS_STARTUP_CONFIG_PATH "/etc/kiran-authentication-devices-sdk/iristar/config/sdkcfg.ini" ++#define SYSTEMCTL_RESTART_SERVICE "systemctl restart kiran-authentication-devices.service" + + #define IRIS_FEATURE_LEN 512 // 单个虹膜特征数据长度 + #define FACE_FEATURE_LEN 2048 // 单个人脸特征数据长度 +@@ -122,13 +125,18 @@ MFIriStarDriver::MFIriStarDriver(QObject *parent) : Driver{parent} + + MFIriStarDriver::~MFIriStarDriver() + { +- if (m_driverLib->isLoaded && m_irsHandle) +- { +- bool enable = false; +- m_driverLib->IRS_control(m_irsHandle, IRS_CONTROL_IRIS_LIGHT, &enable, sizeof(enable)); +- m_driverLib->IRS_control(m_irsHandle, IRS_CONTROL_CANCEL_OPR, NULL, 0); // 停止当前正在进行的流程 +- m_driverLib->IRS_releaseInstance(m_irsHandle); +- } ++ KLOG_DEBUG() << "destroy IriStar driver"; ++ /* ++ if (m_driverLib->isLoaded && m_irsHandle ) ++ { ++ KLOG_DEBUG() << "start release IriStar Instance"; ++ bool enable = false; ++ m_driverLib->IRS_control(m_irsHandle, IRS_CONTROL_IRIS_LIGHT, &enable, sizeof(enable)); ++ m_driverLib->IRS_control(m_irsHandle, IRS_CONTROL_CANCEL_OPR, NULL, 0); // 停止当前正在进行的流程 ++ m_driverLib->IRS_releaseInstance(m_irsHandle); ++ KLOG_DEBUG() << "release IriStar Instance end"; ++ } ++ */ + + if (m_libHandle) + { +@@ -137,6 +145,25 @@ MFIriStarDriver::~MFIriStarDriver() + } + + m_driverLib.clear(); ++ ++ /** ++ * FIXME: ++ * 1、IriStart 设备不支持热插拔 ++ * 当设备拔出后,调用SDK中的IRS_releaseInstance释放资源,IRS_releaseInstance会搜索设备,如果没有搜索到设备,将一直循环搜索设备,导致进程卡死 ++ * 如果设备突然被拔出,则IRS_releaseInstance函数无效且一直无法释放资源,造成内存泄漏 ++ * ++ * 2、注意,即使在设备存在的情况下,调用IRS_releaseInstance 释放资源,也会失败 ++ * 注意,即使在设备存在的情况下,调用IRS_releaseInstance 释放资源,也会失败,在IRS_releaseInstance函数中阻塞 ++ * 查看堆栈,调用关系为 IRS_releaseInstance -> pthread_cond_destroy ,发现最终阻塞在pthread_cond_destroy中 ++ * 查阅资料,pthread_cond_destroy 用来销毁条件变量,但是销毁其他线程正在等待的cond将导致不确定行为,并阻塞。 ++ * 该设备驱动在运行过程中会创建非常多的线程,由于设备SDK提供商对线程操作的不合理,导致了阻塞。 ++ * ++ * 由于不知道驱动SDK的源代码和具体线程操作逻辑,此问题无法修复,只能规避 ++ * 因此,在释放对象时,暂时重启设备管理服务,以释放资源,进行规避 ++ */ ++ KLOG_INFO() << "restart the service, because IriStart device cannot release resources"; ++ QProcess process; ++ process.startDetached(SYSTEMCTL_RESTART_SERVICE); + } + + bool MFIriStarDriver::initDriver(const QString &libPath) +-- +2.33.0 + diff --git a/0005-fix-When-the-driver-is-enabled-the-device-is-scanned.patch b/0005-fix-When-the-driver-is-enabled-the-device-is-scanned.patch new file mode 100644 index 0000000..9d81dbb --- /dev/null +++ b/0005-fix-When-the-driver-is-enabled-the-device-is-scanned.patch @@ -0,0 +1,386 @@ +From eb1053adb52785aa02992690d5444d4068e0e780 Mon Sep 17 00:00:00 2001 +From: luoqing +Date: Wed, 17 Jan 2024 16:32:34 +0800 +Subject: [PATCH 5/6] fix(*):When the driver is enabled, the device is scanned + and the corresponding device object is created.When the driver is + disabled,the corresponding device object is released. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +- 启用驱动时,扫描当前设备,创建对应的设备对象;禁用驱动时,释放当前对应的设备对象 + +Close #25387 +--- + include/auth-enum.h | 15 +++- + src/auth-device-manager.cpp | 69 +++++++++++++------ + src/auth-device-manager.h | 1 + + src/config-helper.cpp | 48 +++++++++++++ + src/config-helper.h | 6 +- + src/device/device-creator.cpp | 1 + + src/device/finger-vein/fv-sd-device.cpp | 1 + + src/device/fingerprint/fp-zk-device.cpp | 1 + + .../multi-function/mf-iristar-device.cpp | 2 + + src/device/ukey/ukey-skf-device.cpp | 1 + + src/utils.cpp | 24 +++++++ + src/utils.h | 4 ++ + 12 files changed, 149 insertions(+), 24 deletions(-) + +diff --git a/include/auth-enum.h b/include/auth-enum.h +index b9e9e49..1b663cd 100644 +--- a/include/auth-enum.h ++++ b/include/auth-enum.h +@@ -26,7 +26,7 @@ namespace Kiran + #define UKEY_CONTAINER_NAME "1003-3001" + + #define UKEY_SKF_DRIVER_NAME "ukey-skf" +-#define IRISTAR_DRIVER_NAME "irs_sdk2" ++#define IRISTAR_DRIVER_NAME "irs_sdk2" + #define FINGERPRINT_ZK_DRIVER_NAME "zkfp" + #define FINGER_VEIN_SD_DRIVER_NAME "sdfv" + +@@ -55,6 +55,19 @@ struct DeviceInfo + + return false; + }; ++ ++ bool operator==(const DeviceInfo& dev) const ++ { ++ if (this->idVendor == dev.idVendor && ++ this->idProduct == dev.idProduct) ++ { ++ return true; ++ } ++ else ++ { ++ return false; ++ } ++ } + }; + + enum GeneralResult +diff --git a/src/auth-device-manager.cpp b/src/auth-device-manager.cpp +index a9f5fb0..481caf3 100644 +--- a/src/auth-device-manager.cpp ++++ b/src/auth-device-manager.cpp +@@ -183,43 +183,53 @@ void AuthDeviceManager::onRemove(const QDBusMessage& message, const QString& fea + // TODO:是否需要监听配置文件的改变 + void AuthDeviceManager::onSetEnableDriver(const QDBusMessage& message, const QString& driver_name, bool enable) + { ++ KLOG_DEBUG() << "set driver name:" << driver_name << "enable:" << enable; + QStringList driverList = ConfigHelper::getDriverList(); + QDBusMessage replyMessage; + +- do ++ if (!driverList.contains(driver_name)) + { +- if (!driverList.contains(driver_name)) +- { +- replyMessage = message.createErrorReply(QDBusError::Failed, "No driver with the corresponding name was found."); +- break; +- } +- ConfigHelper::setDriverEnabled(driver_name, enable); +- replyMessage = message.createReply(); ++ replyMessage = message.createErrorReply(QDBusError::Failed, "No driver with the corresponding name was found."); ++ QDBusConnection::systemBus().send(replyMessage); ++ return; ++ } ++ ++ bool oldDriverStatus = ConfigHelper::driverEnabled(driver_name); + +- if (enable) ++ ConfigHelper::setDriverEnabled(driver_name, enable); ++ replyMessage = message.createReply(); ++ QDBusConnection::systemBus().send(replyMessage); ++ ++ // 若之前驱动已经关闭,启用驱动后,遍历设备,加载可用的设备 ++ if (enable && !oldDriverStatus) ++ { ++ //NOTE:注意,此处是从配置文件中读出所支持的Vid和Pid,获取不到所支持的设备的BusPath,因此这里返回的DeviceInfo的BusPath为空 ++ QList devices = ConfigHelper::getDeviceIDsSupportedByDriver(driver_name); ++ for (auto device : devices) + { +- break; ++ if(!Utils::isExistDevice(device.idVendor,device.idProduct)) ++ { ++ continue; ++ } ++ device.busPath = Utils::getBusPath(device.idVendor,device.idProduct); ++ handleDeviceAdded(device); + } ++ return; ++ } + +- // 驱动被禁用,将当前正在使用的设备释放掉 ++ // 若之前驱动开启,现在关闭驱动,将当前正在使用的设备释放掉 ++ if (!enable && oldDriverStatus) ++ { + auto devices = m_deviceMap.values(); +- Q_FOREACH (AuthDevicePtr device, devices) ++ for (AuthDevicePtr device : devices) + { + if (device->driverName() != driver_name) + { + continue; + } +- QString deviceID = device->deviceID(); +- int deviceType = device->deviceType(); +- device->deleteLater(); +- QString key = m_deviceMap.key(device); +- m_deviceMap.remove(key); +- Q_EMIT m_dbusAdaptor->DeviceDeleted(deviceType, deviceID); +- KLOG_INFO() << QString("destroyed deviceType: %1, deviceID:%2").arg(deviceType).arg(deviceID); ++ removeDevice(device); + } +- } while (false); +- +- QDBusConnection::systemBus().send(replyMessage); ++ } + } + + AuthDeviceList AuthDeviceManager::createDevices(const DeviceInfo& deviceInfo) +@@ -258,6 +268,21 @@ AuthDeviceList AuthDeviceManager::createDevices(const DeviceInfo& deviceInfo) + return deviceList; + } + ++void AuthDeviceManager::removeDevice(QSharedPointer device) ++{ ++ QString deviceID = device->deviceID(); ++ int deviceType = device->deviceType(); ++ QString deviceName = device->driverName(); ++ ++ QString key = m_deviceMap.key(device); ++ int count = m_deviceMap.remove(key); ++ KLOG_DEBUG() << "remove device count:" << count; ++ ++ device->deleteLater(); ++ Q_EMIT m_dbusAdaptor->DeviceDeleted(deviceType, deviceID); ++ KLOG_INFO() << QString("destroyed deviceName: %1 , bus path : %2 , deviceType: %3, deviceID:%4").arg(deviceName).arg(key).arg(deviceType).arg(deviceID); ++} ++ + CHECK_AUTH_WITH_1ARGS(AuthDeviceManager, Remove, onRemove, AUTH_USER_ADMIN, const QString&) + CHECK_AUTH_WITH_2ARGS(AuthDeviceManager, SetEnableDriver, onSetEnableDriver, AUTH_USER_ADMIN, const QString&, bool) + +diff --git a/src/auth-device-manager.h b/src/auth-device-manager.h +index 95e68e9..150a372 100644 +--- a/src/auth-device-manager.h ++++ b/src/auth-device-manager.h +@@ -62,6 +62,7 @@ private: + void onRemove(const QDBusMessage &message, const QString &feature_id); + void onSetEnableDriver(const QDBusMessage &message, const QString &driver_name, bool enable); + QList> createDevices(const DeviceInfo &deviceInfo); ++ void removeDevice(QSharedPointer device); + + private: + static AuthDeviceManager *m_instance; +diff --git a/src/config-helper.cpp b/src/config-helper.cpp +index 8fc5a47..880296b 100644 +--- a/src/config-helper.cpp ++++ b/src/config-helper.cpp +@@ -113,6 +113,18 @@ bool ConfigHelper::driverEnabled(const QString &vid, const QString &pid) + return enable; + } + ++bool ConfigHelper::driverEnabled(const QString &driverName) ++{ ++ bool enable = false; ++ QSettings confSettings(DRIVER_CONF, QSettings::NativeFormat); ++ QStringList driverList = confSettings.childGroups(); ++ if (driverList.contains(driverName)) ++ { ++ enable = confSettings.value(QString("%1/Enable").arg(driverName)).toBool(); ++ } ++ return enable; ++} ++ + void ConfigHelper::setDriverEnabled(const QString &driverName, bool enable) + { + QSettings confSettings(DRIVER_CONF, QSettings::NativeFormat); +@@ -147,4 +159,40 @@ bool ConfigHelper::isDeviceSupported(const QString &vid, const QString &pid) + return false; + } + ++QList ConfigHelper::getDeviceIDsSupportedByDriver(const QString &driverName) ++{ ++ QList deviceInfos; ++ ++ QSettings confSettings(DEVICE_CONF, QSettings::NativeFormat); ++ QStringList deviceList = confSettings.childGroups(); ++ for (auto deviceConf : deviceList) ++ { ++ confSettings.beginGroup(deviceConf); ++ if (confSettings.value("Driver").toString() != driverName) ++ { ++ confSettings.endGroup(); ++ continue; ++ } ++ ++ QStringList idList = confSettings.value("Id").toStringList(); ++ for (const QString &id : idList) ++ { ++ QStringList idItems = id.split(":"); ++ if (idItems.count() != 2) ++ { ++ continue; ++ } ++ ++ DeviceInfo deviceinfo; ++ deviceinfo.idVendor = idItems.value(0); ++ deviceinfo.idProduct = idItems.value(1); ++ ++ deviceInfos << deviceinfo; ++ } ++ confSettings.endGroup(); ++ } ++ ++ return deviceInfos; ++} ++ + } // namespace Kiran +\ No newline at end of file +diff --git a/src/config-helper.h b/src/config-helper.h +index 91cf895..0e1a5f5 100644 +--- a/src/config-helper.h ++++ b/src/config-helper.h +@@ -13,6 +13,7 @@ + */ + #pragma once + #include ++#include "auth-enum.h" + + namespace Kiran + { +@@ -50,9 +51,12 @@ public: + static QStringList getDriverList(); + + static bool driverEnabled(const QString &vid, const QString &pid); ++ static bool driverEnabled(const QString & driverName); ++ + static void setDriverEnabled(const QString& driverName, bool enable); + static bool isDeviceSupported(const QString &vid, const QString &pid); +- ++ ++ static QList getDeviceIDsSupportedByDriver(const QString& driverName); + private: + }; + +diff --git a/src/device/device-creator.cpp b/src/device/device-creator.cpp +index 1fb760f..90ea201 100644 +--- a/src/device/device-creator.cpp ++++ b/src/device/device-creator.cpp +@@ -45,6 +45,7 @@ DeviceCereator::~DeviceCereator() + { + } + ++//TODO:将设备的BusPath作为入參创建设备,处理存在多个一模一样(vid和pid相同)的设备同时存在的场景 + AuthDeviceList DeviceCereator::createDevices(const QString &vid, const QString &pid, DriverPtr driver) + { + AuthDeviceList deviceList; +diff --git a/src/device/finger-vein/fv-sd-device.cpp b/src/device/finger-vein/fv-sd-device.cpp +index 442f227..80ca4be 100644 +--- a/src/device/finger-vein/fv-sd-device.cpp ++++ b/src/device/finger-vein/fv-sd-device.cpp +@@ -42,6 +42,7 @@ FVSDDevice::FVSDDevice(const QString &vid, const QString &pid, DriverPtr driver, + + FVSDDevice::~FVSDDevice() + { ++ KLOG_DEBUG() << "destroy FVSD Device"; + if (m_driver->isLoaded()) + { + acquireFeatureStop(); +diff --git a/src/device/fingerprint/fp-zk-device.cpp b/src/device/fingerprint/fp-zk-device.cpp +index 3a818f3..659e9ab 100644 +--- a/src/device/fingerprint/fp-zk-device.cpp ++++ b/src/device/fingerprint/fp-zk-device.cpp +@@ -49,6 +49,7 @@ FPZKDevice::FPZKDevice(const QString& vid, const QString& pid, DriverPtr driver, + // 析构时对设备进行资源回收 + FPZKDevice::~FPZKDevice() + { ++ KLOG_DEBUG() << "destroy FPZK Device"; + acquireFeatureStop(); + + if (m_driver->isLoaded()) +diff --git a/src/device/multi-function/mf-iristar-device.cpp b/src/device/multi-function/mf-iristar-device.cpp +index 27e1809..58ed89c 100644 +--- a/src/device/multi-function/mf-iristar-device.cpp ++++ b/src/device/multi-function/mf-iristar-device.cpp +@@ -33,6 +33,7 @@ MFIriStarDevice::MFIriStarDevice(const QString &vid, const QString &pid, DriverP + m_driver = driver.dynamicCast(); + m_driver->ref(); + m_driver->setDeviceInfo(vid,pid); ++ setDriverName(IRISTAR_DRIVER_NAME); + + qRegisterMetaType("EnrollProcess"); + qRegisterMetaType("IdentifyProcess"); +@@ -44,6 +45,7 @@ MFIriStarDevice::MFIriStarDevice(const QString &vid, const QString &pid, DriverP + + MFIriStarDevice::~MFIriStarDevice() + { ++ KLOG_DEBUG() << "destroy MFIriStar Device"; + m_driver->unref(); + if (m_driver->refCount() <= 0) + { +diff --git a/src/device/ukey/ukey-skf-device.cpp b/src/device/ukey/ukey-skf-device.cpp +index 82bd13d..c8feb7b 100644 +--- a/src/device/ukey/ukey-skf-device.cpp ++++ b/src/device/ukey/ukey-skf-device.cpp +@@ -51,6 +51,7 @@ UKeySKFDevice::UKeySKFDevice(const QString &vid, const QString &pid, DriverPtr d + + UKeySKFDevice::~UKeySKFDevice() + { ++ KLOG_DEBUG() << "destroy UKey SKF Device"; + int index = m_existingSerialNumber.indexOf(deviceSerialNumber()); + m_existingSerialNumber.removeAt(index); + KLOG_DEBUG() << "destory device, serialNumber:" << deviceSerialNumber(); +diff --git a/src/utils.cpp b/src/utils.cpp +index 3061263..0e11b04 100644 +--- a/src/utils.cpp ++++ b/src/utils.cpp +@@ -101,5 +101,29 @@ QJsonValue getValueFromJsonString(const QString& json, const QString& key) + return jsonObject.value(key); + } + ++bool isExistDevice(const QString& idVendor, const QString& idProduct) ++{ ++ DeviceInfo deviceInfo; ++ deviceInfo.idVendor = idVendor; ++ deviceInfo.idProduct = idProduct; ++ deviceInfo.busPath = ""; ++ QList devices = enumerateDevices(); ++ return devices.contains(deviceInfo); ++} ++ ++QString getBusPath(const QString& idVendor, const QString& idProduct) ++{ ++ QList devices = enumerateDevices(); ++ for (auto device : devices) ++ { ++ if (device.idVendor == idVendor && ++ device.idProduct == idProduct) ++ { ++ return device.busPath; ++ } ++ } ++ return QString(); ++} ++ + } // namespace Utils + } // namespace Kiran +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index 1c28065..9ba5598 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -28,5 +28,9 @@ QString getDeviceName(const QString& idVendor, const QString& idProduct); + + QJsonValue getValueFromJsonString(const QString& json, const QString& key); + ++bool isExistDevice(const QString& idVendor, const QString& idProduct); ++ ++QString getBusPath(const QString& idVendor, const QString& idProduct); ++ + } // namespace Utils + } // namespace Kiran +-- +2.33.0 + diff --git a/0006-fix-build-fix-file-install-path-error.patch b/0006-fix-build-fix-file-install-path-error.patch new file mode 100644 index 0000000..06239db --- /dev/null +++ b/0006-fix-build-fix-file-install-path-error.patch @@ -0,0 +1,29 @@ +From a0488124de99b326d9abc38228251146ed6cdc6d Mon Sep 17 00:00:00 2001 +From: luoqing +Date: Thu, 18 Jan 2024 11:25:40 +0800 +Subject: [PATCH 6/6] fix(build):fix file install path error +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +- 修复文件安装路径错误 +--- + data/CMakeLists.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt +index 617cd14..a6ae20b 100644 +--- a/data/CMakeLists.txt ++++ b/data/CMakeLists.txt +@@ -11,7 +11,7 @@ endif() + file(GLOB CONF_FILES ${PROJECT_SOURCE_DIR}/data/com*.conf) + + foreach(CONF_FILE IN LISTS CONF_FILES) +- install(FILES ${CONF_FILE} DESTINATION ${SYSCONFDIR}/dbus-1/system.d) ++ install(FILES ${CONF_FILE} DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d) + endforeach() + + configure_file(kiran-authentication-devices.service.in +-- +2.33.0 + diff --git a/kiran-authentication-devices-2.5.1.tar.gz b/kiran-authentication-devices-2.5.1.tar.gz deleted file mode 100644 index 68bb364ec71bc9b6ea0d2e979baed415251cfa9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65592 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

rpWa<<{Xn6K=aWYgAde+b8FlabY@mB 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 P0Fa(25WYaS;6VQee`=xm diff --git a/kiran-authentication-devices-2.5.2.tar.gz b/kiran-authentication-devices-2.5.2.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..9f945c8190212f5c93f8a84bd144f001d071198e GIT binary patch literal 68707 zcmV()K;OR~iwFP!000001ME9%QyW*7`K(`YYbsNQ8IVB+dzRWq2_YOMLQ*7=#Ffpe zn%0%nL#tcqZh_g^sUnWSV1prvp8+Q(@qiQ=fpqs3t3Uj3W4MQ)Z2F=*L&;)rDMnYdwu`cGQYZhzH4041f^Rj8wCgf zNfZo8Rl2iuLK3Oo9q{-214sYIJTMGwAoi>sOxtFX4Z` zC<{uyJEv+z;ZPGCbn*J%caZ-fG@AV%=!N^&ei0-7%Kbkm=rkKwQpS`_2M9T-s(G3E zB1#s*;aw?f6krjqK3{itw=W8DP!{r(ufY{6m3%=FLPZ}@vr$C-Bh(aK0EJ7P`!n!Dw zXf{SCWl5m`i_)CILBJW}JFBC_hE}#Wq%}bT)O?6{K7iUVS)GJ`62PTML2f;s^DvDo zF?!mF0t+vGs-SAdpsb4HdRz&nfT~P6jw7Li6arI6g)u6>2ywWqh)l~SJizc^MRF-a zFep=?39(7KsH&C@K$#S#^g)dZZ~ySj5$7ag&DxLo77X;(#zbG|}QI&J4#5WkPvTXn!GuWPS0|J~UXj3dB4HaF-;>L?$3j>i`6cjHcq7!3+>&=|3U797@eB4?u6jnz`&vVL}*| zsGJQdifS}gFowlbBq{+eEZTwrm2y#uN=3aEXl5noRvh9nJK z2$A5_gEpgwy@chRW}TwM9PCiT1WSpUR5IQ!s|FtdgbD9D?v4~o&<&kPc#F~!D_I!t zdeBH0pi>E&5zjg9<+NF-u%$tom!KFPIpSFhD_Init(~ruORGqJ09@h*BGABaa4?1lW2CDYDuEJ0U61LtI=J%1corVWxP3DuQfG@$%%3 zT}asr;oFBLO$YCJW+K$di)ZK806Esv(*R&wqc9rcf3Pdi2<9aZ)_a=vwHXQ!%fShs z#&I)L0FcPV;RCk=uMZ}1B?6Ptq{8c208v`14Vx5`!X%f5*Bd8w+fKXqcZkYZlv%-N zmyiGHCZg!xhC=mKdmmf&-X{|7{Soo^zdurc3OP?9K~WVrm&q}e=EEEZm%L21mrO$^po-JJ}OjHl!5DGaqh)zNa!$U0Cb75=ac5t(S%3Tx6tT5ViR z1e2+72L4S)g3(Mm{9Za!XHiE;hf@auT6YMVPEqH$HYCy3WhOu!_7kj}q&b+c>)nOt zRtg7mULd(?-PKVvQ{$k^VQwae7!N3w?l4-1IZ5#rotmr<3 zjK)sS2ANhm4dYl_3@|iB6`RLw-HVijAf?Q{ka&1VX(1L-SV?8ni~_V;y+LMsK5ISB z1#Eak3F47QcV}{E8RdfZg~Lw6%;myeUmVCgq8tKBLG1H^7S6nND3~lOXjwNaaftD` z#&L!cdkh(kji$mw{2`r5hTk5IB*U3^!pT%59`mJzF;A0(>kqzBh;3oa)5m!q@%#P0 z1K-)c%=@1_#6w;IL;u8g3@>>9)6>)2-}3$^@ap}~FLE6nD@$_L_d9<{HD0~g|Mguj zk^i9)VVs?;b-!`?r@R1uLH_p!`g)u5KhV?P^D6&;fy*Zr!MmBFq)5ebF{4o!&@`Iu zu-gnd<_~oGd@%q05sqmcheZpZNqq9@I2jv_C}3AvhT|Oqa*~qGKx-U_k+dT*4xL)b zNqJ)Tw4u&Mgtv`;7eqqwkwiQej-^vDOlXu04VHD1`cTHvLM&_8feB3b`7#7(T2*CW zR~pY}bSh|KAyZVdWtn!o6^)u?CdjXgWR`39;qI)sc9+)nq8tOd|B&dzn-^7#j*Ehe&U4=K-LME33Od zoeaiO(O?=J1F`~QG|FUr{ZEekd~J9X0l{<-fPX&v`k!Dnelr|OXJWyT@Sl$w;6q*J zLujW@WUeS{G?SCSQVyw9d^8yfXBdI3U02xj9wng zvZKAKO?Bv+*x9;@WZNv4$LUmeQBWWfw9bPEmsi)%a=k?Tk48e_SStL}c^qE<1A+b% zEx-Tu91HZns{g;hMcQ4~>bg0zWp3On%; zGX3~;4v1Q-_gol|{_*KNQonPivVFn2v1Z+S29MR9JJyXae|-8mlc&c|%#F`0Pv(90 ziv+J|*0oJ*;av6716+KxR$bV@#V^iUbIU-8b23-gTNwjC*8G=g&1w@9yF2qP-9PsU+y1z+dj)7#cE1M|%(-nx`(8h> zF3weU=D63mmX2Ml&85orH_fiT`NUe>Xf+Ba1KDY9ZC9SmSkrew8nngCdUg3@&*Nh4 z(Gz~Zf8&elg?r}WP5xNfUIc2^r?>VmO=IwTBtkPm-Y-E~-99MSkvPwF=59A1U@uUv zZR~I~^vCiYNB@AMe`WhKbM7zypY(@<#CZWDs6Dm2XQ9q;=>ByGnJZA7yi%i{&sNY( zbC36@Zz8X|m#q0~?xSljz^`rY03Gwv28!X_)Sj=HOJ6sc4?`G?&Lil`6Z7d(ZSP+7 z?rswr7}T6!tUcfIid5O1*}o1sd9lfc)r(tD2QV7WD9pl)x%_PtlpBh3M4Q|>V|}rTQ32YWS?7W^6`Q6cHdnW>@N7M3 zXe2y|x9{7kIUGSeEc1J;8ZLcO`|f+L4rqtTvpHYecnX1J>$0>6E9lUjTjtWd1D$s( z1m9cTeS~}07R>YOn0Qc)z%PKnEasXEg`B&7NFubhX>V*mwMVloErZNl5)^}};1!_q zWGmr*u6>N@we`OT-5-3t&55YJu<1CF(`Y!3~4 zUqPxelWtn`chQ(^W&hlwb!Ch5Xp)_D( zZ{Oi|1gh0rIBo3jUV|I#o)>07ddyqL*4D=}=InRS@jvnI0hP4Y1U}QCy77%-F9hw7^*j`85t==C7;`~kB+mG9WJpN!+^iE#_Vm8{8I0~+3!LhK z26Uho9Sj!ColD?Fb8d^5aPCW_VbkFy-rQcWR#$!sLC8o>li3FxHVd7$^6Ys%^S->j ze{BXgawh?^jRiPF@RLbp8+>-#IgsE~hi9$Y=J$YCTZ7&`!||L%V#)ZIo$B>Hq{vzW zXAcz)#{hPZ$Ba8ytb1!%lh@Yaq*mG8!z#3N9;@9RIA({3KSVl)$?~Fk|LTuVR~#V% zbs@lGHiLy3)&{1lJ9n!q8z!U)h&8tYY5{48Bl~p%s~J03aM3vDxMk3?mmk4I=Gqhw zL@*p(dS=etu5I4tn4q6Wiet7E*!n$2@b2Ve9#l@gpB*P3?SH&(JzU>EcOJ+&F%(iu zQ<{`77^Fk&B=4W1g7yJy#J;*q}Qk1QEZhlaxF$Q~&{(YDJw0N*0(aS@vzF zj-nu}fi7_f7lkRJj^VGHL|_slx)+rQ#(>Xz+<&Q~IisI`Ba$Mi_;C8&U@}bLK9P*S z6B%OfC`m9zeiKZ=>Tk$kFcnF4k#{5MH{zpd0@%r5EFB4_NPHOZPLa1Fv7s&!26#A0 z!tW)L;S|7AEyQi5m)0ZcB*Fcc`X=(@22P_`7zH4zFOHxc^N-DGS;Zd$aPg zuT*$kXhfqWP0g0scN$$7dtIO>eznct`mrF(O1hy*V`a8rhnXv)UJzuNjKNKL41W&5 z7;P|J2McCflTI=z83jfmr{cHBEUMdtGyi17e$i6@1ld_cKRFTma(7G^m{cDhLdhFrUEJ3Vg$i~ks^NFu?zpx}K%6PAp~ydir8;1pmJHT( z_$t%T6UZsb3U((H5~iKUqWH^g7bytPMv6k#v8$TP!CoY&3o<$!Si{Yg9#@}!@4$g>Ic>D%U=Z@qaEaNw`{F%*^^^KkN zdS?@*3Iv|WzGh>!d4iWfAuk|*rmWP^#q7$BtrDUn&t?Sd3=QtVRscI290Q_Ah8q>S zEr8yII~9iBYnKlCt77X+YbQ^c{&f+5rXO9_COs?}3rV0L@&XT}@ zJXQS8d##vC(i)^|QGlM@RMY`xxG+z~d1U8j2FmdWo=i|@XOsTl>^*CDTSb!J@2_a& zb8@VphuQl;Jvote)m>FxU0q#OUEPi2y@ac`One3W!tCRP z`J`eFLC|zu13}x2`etT&fY6_=#Irz~!%4nv>Cr8aEXYljSJY8Hgp7Tq$!(^9i}KG2O77X^i09Z)Uq9h4xkkjIdYODh)_a zrAyes+XRR<{Fq8(QA@AF$S!Xid<2il^1wn-Opu8t;|h35n69J1za+z-OwX)qCoF)_ zqA@0)1Q&}X=0l5%RLw*PMd9CUZ25FtnVV0t`PgC@AkE0&tA%D3Wpvd}$5yx(1{K4h zrO=$5usL_WM&2D2RpRn2_;pB4)cBHEmS>gFB1alc$gi#dVfaD-hIT6nbQvS@*b-un zvz3I*VHl1fz{htEE809Gu1+DCk23a~nSQlVlILIeJ1LNVOb2urL$MbUtO5qmSz_pMF`Ut0uD(Ct1I zSAZy@awe9XXS4H4R8DYtkfU>pN@CteU#MN;M*wEdv`u`Qm`hpB#YL_kq9uK zWPyN%kfRakAgDSgM`vZ|@8l_2jyiBPzybZz0mqb9R}^GgXs%)<(H zO@hkIgXI8fD#RyT%ZKmEvqnhdA{%u~z88!sy*5g~XOR+lRz8|Jz+nKjk2`g&Ss$h~ zOV#+e$w+q9S(vhfLPrlYraOhibw! z!ZnS}mQ}ib3acl>bMrYsaBOqjHB`6CbqZQ=gHvv%kk!%6L}uZHMUBQn z<#JkBd?x1%YZf}aR4A4-qL=U41Y-4VAZ&{X>tq$Jl|1qo(2=_!<|1rfK>EiGC-x$W zE+UH0JMYVQT;%`7jV5j=ft|L&drd++Su{l0qV z^4{Isdpqw*dyhWB1swWf|5=jy`qVK=eng*R@{fUyL<`@?`k%2AV>Dw^hZD#p5fVsH=`ke9IDSa%H<$tnc z#~5X-Gk@5b^9%z%qWpR)SkX%?5I_J>3O@8omPbkq3W;WH$o>yI`?s%4d;j|g+FRYZ zSAGA>>dsA`7x3iqt^Lbi(VKfbN8-tYPxu=B(~GzEf4IDR^%}hA*)m3%TLhXn1bl9s zR$%IFHNpbIS>rT452fH@{8Re(L2!seemXe+X=q?*Xt;O&(-Ayw`~M)DfZFK)q4Ck- zy8S=n<3m0De*}-K?LWBpBylb=Ob}kKu@@h`iC2b9hCFP}X zDiT|XhH=RfH{+aD%PFIjD3Mx;Dtv>DQ2fRx|G+~eK>vFsHK(in2uIN1s9L-4h0g&O z!0ucP3UJ~R0D-x?29R(vDdgA4O4lXY^D?&>n?e6Fl8PvcU{P8dW*Y2***qS=`naiy z9~#nc)*!%qgpsi&9bWC7lJBRLWCa{30 z0wF*ZCiq=)z!l6+Sl|j^6CSkM;pLDOzks8+psY@=vv?R!i6pyuADFUPv!kpdM7*uf z9+7<;S@-Br>nw!ACf~&l!KRw0_Pr>7fX0Y5AlvW}gT}7~)|<4c0mT=B-iG?$?flDPYiO%CnM;_s&AAF#b7X;470F*W+lOJv+k_W z)I6pL7=l4L>Vaw^N_77G`GBoD#)*QuI4MIX-4?`3Cf-1{ob^mglje(Q7zUFs`28$~ z3)9$96b>t#=g~o9LCo)`g2h5k%WPw?tTg4p!k%dwMw4j_q++cpj~i+C1PNS_!<&|D zutm6X%9B;s(v@5d1|4Sd6g0Lh7JEBEcTgbq>5o9rfk=(X$qPF25VLNRndQ2~_a^%L1bW~voD6ABa*)k%$+iieh> zT&k~WD-(EfQ!TF-vZA(}$22E8Ku{ns%cV?qEc87G5w3Y={VLgS{5Z&FjTi7$*dU){`?15IE~202sPvbj*K<+2l9p~%nz z$otP0aP?~qbA(F8l9o59pw&JK&_YWo2gKHcU6ZHq1<~y`av;`Cw+~4Ye3X>dyhmX; zZxR>OG$q+-S(ejuZX~61dI3}IR6J*GqPa+cIa5kIgvy8&xfYLB0VH0eK)|8DsgnSb}j|sOVr93hB$*g#pY9=YqY4 z6cqBRzg$4)k#Y%(>5`^XP9Ek&;u?OoK-qp9n7)X_aLUTt#!4#MmR8cyc}-uZoIs1E z>LEzj0fsGUwECgiD(e#;9@gu^@yprDannl#bz0sFaQIkZwC503}Zb z5K^v$xs;1OG0qC&whe15wJqCP6o;;gHt@+o30s2~SKd`!2M0qKmTk^% z17TYt@ZeK}46Q3NuBf z^;8hCBpzn&LWa^-XfPXN0XLj%fL2FSzF7-+D;%O+Y%U3p6K^Ly7 z<@2hVX9J|v2L}dFAM%o`s}*NiQZrMpG{{Gpp{-i4w9NFu zOA1O;$V@50GqeQiDOdSH&zoJpxaV-jf0Knj@w6HLV?!f#`5$8=!@cu=kK*aA|G8TK zYrgO|rGa3xiyh;_q7u(r_|mtpG$?f5D55bXn` zz2kiSAj^EuxKG-41_tB0TaUDL#X-Vzf{r3^pjl@W+>pWb9+U zUGI%4w~G*>pQ($R^l?Hh%Xthah}FL~3`pUZZe^OIAb8#DK9#6# zLc(f05_nsMpot^0x((;sxa;`ER>raB!(}v-&8l^)sZB=G^O@i~S#0WdLTkG~CYGdC zJoKT*uHM?wbA8j?9M1~*j4DZeSv-DKWyVEg zve?*U8@`@bn%qM6il;YDe}0eK@o(F=*D;PZ<9}!feybh-!@c<5kvx58@SF>}-RkM> z2d_(g{G^ZS&J8?{{k#9%y|QDzsb2XXZo9DXYDLF;On(2_lLvT9h15su2F$3w_syf~ z$JeAj^jd6hZ@r6`YTSG8q!RWmgOwnT(x7*AiN*JTV*lRf0^>IyKY4hw`iHOf{&Ir@ zqzkq1C(uJu-$^YyWj8FOzGZkL^*#NsZ|LBT8+6qEP21L1aW4L!?^o|Vs6PCo)W^+s zsDpPpJiLjS&jR-km#Vk_iY(xly%2i-)68D$G9oF4j)%QQ$|L!}zfPf$2Wi9)E zzgPYK-5($S8Luq3^50MH-@tnS2pC=-ar>{;Dn9VXxHc(4ZsS4T%3FR>E_28RatH^E`{ z&l_t+ql9!u&x&U0X=qMXRW}9ELj-eRR2}AU#s+?6fY7sR=b)-q(s0Cz`r;d}Jm?$d z)kjuGc=ds`VWYYR9EXx^hYdzq*#f zk+H#{Is&ojbp!&qpMZ!Tlu2{pnRHPD4n1$3`Ad3S&;OPd?HEU!`5(;xy7PaAhR1s6 zzZ}J5%$8R^*uVXy)OUvK4u13?J&x(r)ERh@)ZLGtesoDM7;~wJKiQtX-4E|ocfP1T zzF6J4=d4Xi;=PYRPjC&f_r*J)Lb$=;oMappg$ZPW;RTovANU6*+D1!B-_#$YP$%zA zj#xN@Qx`Bp;tiZCmny0;!{SV~cJB51pN43r;xkTzpK+!FjZ8d16(`g#aeDUyya{LT z;TLF?-M{$c{x!U3?8--YzE~gWbbi?vUR`$iJ-=zb@w>~Uz9-*btA6n>sJy8dC{JDz zw6~@F5d$6a_T?L5XX;kKU%X#l<{J7Qs zE)N)Lh@)NnXLz{I{~I6g<^LYZb7m#4!JrOX(b_4>ePAKyCQ`AcZzPJrkT+AtXn_(Kc@8t0Qgz8+zUs!c-)Tvy5p2O zz|l7TJ5*=?4UdfW&VM+Hhi2~Ad%oE}>f>Vk8>VyDIB-04dj9M9@M!P+*CTj3=znsI z(8&s{cZQ>l{vQW-zfS+dq|npE3Dw|?o6n0xEB;4#6!EI0D*OSO##+% zcS!-1#KLH}7fL6HY2|eO?3FB6nEHZ9TpR7K3lCZ9oi+gy*vlTPujofJQQ7>yKt9Ym+4k>at|v%_(`t|m3J z7@A#3;Ss$lJoA$uWZW_&#KWHc)m%EWf%$4=`g$z5@=0Y@b_q$$33dCA4 zRu<&bsijafG}i&7I+NbUu+U0!KDCknXrb_u0#fd9=?HAV2{wS9mp`4wqonmR-|T4v z7)hpxNYUCwWrlfb7Cvg46Ix!TgHoAiL4l#LNKLIo2G5rY<9(uiPL0F^ihN+|9q#ymz;&ng$e0U2M0ahXj0X2A&+V~4dR zQzvC5no5M7B{7f)L;~yZsy1<=On$8ZqX37V7$i)~o>cSMLTRGr?J~dP&G{O9agpB$)=cp z&}YC62HEb{cSPR6{TsV~ytjM(oksOz3s7;u$7&v|e`Tlo{wIy#6VmSmi)r`V9sS!e&-OS>O@+o(=7#?Q7-aRmf*hkc?BMb>d`!ufYsFuU@@T-MPuX z?%jG9IgqF^53GdPsWVgX<J+<*4c-xEr9Kq8p z{xiViKZ6b8KkO$I0AkNL0>lnK1jG(C2E@7v0x{-348+SGQY6S0^}(C}@CHCFux<|q zW%P1ZE7;$xRr0hM4z~zdZ*>tfvfiAd%U`FZ7GWf?U7|4cDzRuk9@7NA0)F8Azxb`M zkG|EGt|{6Kh9qTjXPJWLrzZsR@^+U@}oG@B5d9I*k`77QO?=-{Te9ZO^ z-_W*1FFQfMruy-Judi+^R@aY#fglT9Z(ktzzvE=XcMCT!>z!a1B&k@^&ZWyLZ=$Ql zr6#PhH7!g{82-+>nk$0(*i2_i1rkRhgAsSlzDdl2E0vOett9(bbA`-?SClgPES1Ht zDIZIv7L}-+N_qRw9DChP5=*5^XHy2nUPfTBd}Jj`-3xWBn9)pJ4lSgV2pE)$2^nK2 zSucByhe?{T_BDsc{{pWBh)QaDW5W3PLIWJh_=-CeP7J(;0loo(xC}ONG<6dEQAbOB z>|8qMZ2-Ax(>iLf+e#f^d(ANb78YZ3VBpTI%=LScbP2VXGx&kAIneGN5Lss>5#)fD zPq@Q$p;Pjja^CD;!FadKZq(h4n$Zk>jrg{}@yqY{mYwdzbO;^a$)Il0%`Jzu2r&UF za!vcytzwA;tfIOXv04hiF(?3X4E3=oMlij#taH+96rM6rH!baknucQ*J{oslXMNV5 zkzplqU|zCL*~yx4^o3>|WnGM=CVWLihrwFbfV;RfqeNHa4rfu;RK4IiI$FM>HkwQA z$lQ;-#$(ThrvWBqE*guYDKaZBlT}EaS_`o=SudBiU6yPI*V)fVS~JDxzJsljnRGd` z&csU6Tuxg(aY8PYz+^kdrL-G5yTneGow}7#`I*+o%yIfGu-m$SIi^H0`k9QS5=nH+ z`ioxHkN>^QieRFH=owmK@C*2e0g{%Ri22_*npzJP-@k~;WWm@-s_dn< zdePmnN`Pm8S7qkN?zc$O5oay&26eV_K#S9PuMZh)4g=-qSTVavYae@>i^Q6&J}sB7x)a zc&4qY1+W0ux4=T!1$mUL+5&+k2_J}s@o`7$qbhN#Qf0@o9wJpzj*|E#D!WpVQ>9c{ zwk7M&=ycLk{zAHYW_D+HXBWW9k@6r`Nk`7~^vrbkbocc1%wkl6Ny7^SFvrK+zpHIL zc<^AuLjX0?#HsDZhUTTFJ%&Bw;58t7f;DNLjJIfH>ZtQK9f|t(=bQLhA^wY*5998n zdxe|p=?X#M{P=HafB$ak`|o?j-EZ^%zK)-roi)#F!M`8m9%Va-_FCjBlQTP21Vlru~QXG5A4uICzcS7}?%ge&7QyLO-R3i0WgM9Jv~nxV zDyX>D=84_D4-l9rXH;CfeY$L?)v|~4GMakX!Z7rF>g)Y8E4b+42j-2 zfZyXVHiI-&Va!$VgPWZO#yP;{-n}mF?yCC2#H#4<7=>sd^J{v=Sg{FFpV|Q>?0VBA zQFD)Adv|18FhhJ}s_plJixI^P+)LR1Tn58_L91D|p3PAjzSFYZ7_rRBVCsy6kXCt( zYnJYU&>I$p`BPYG!|tp$@P~LHvJ5uk)bL;r(ug54Zv192xmv0{hNbjo)zhm#=s4+F z8Q_KKqrTq%5$MWg5H@TgbrMe_V4o7qML-N|=+1qwa%5EQM@W!cjqdbe<E2lWBY&8U$Acffel6JFRRL4e;bQ{HKeDR{0nCzf%RELVvzB_L0y?j9 zD)bCKUttX%`6GWirHSdi-h9&SiP2>!7R8v7n0vu6nD~9mA5E9T84<3nSIv%-B3irK zAIzsq@n3J(O$GnElZ(M3{u2VbR?{Dt=^@zX=iTc;I0gldmTomx)DzUiXx8zVAf|T5 z-Qkrnf+73t3nrLEw@{6Sb7*y1rbYRvZC$abT61vA0q<iLXwAp<6;frpJP6e96J2eIM_*v`|L@smewh^di^L35SL) z2l~{aeNMVM?$$(WBWq&lLvVSFh~wXIxn7PqvQS4zqSdhqiJLXM4cK1l=$O#J!~_O} zj|GF2_I^Acpme=7hwa9qwH8CjGkyg^VjM&24zB!QK3f78SahR_`FzasCQvT1C17iB zX)IZ2nkI!EQM?JGONP@iToP+HXbwn0x@b11e$LE9&ZOOzC99xyd-R3pS}>91Nel|M z0*`~q!*l#V)Qr)Noli`2w*;k}EdkFpRq_b)85glgUK=kfdClkX+Cpv~aK`cUNhGOZ%(E1ecYE0?%>;XU>M2;AGyG zBlr^Q6S3tGJOig$MN5e(!3uO{A8h8}{AMBRxWlSq9dlWiVI3jKWF6wkU>z>}qwb_n ziF^FP=ez}f8yCDH$|M37$CZY6bh4cGD~ShPZ6uG#51x;}H#DYX$ny z1iP_p_y`2XbB#s|+;qA~uw;h~%nD~qynf{3nX{`mD+yX)SU(ei{$MeJ%Ou!iL} z{(~~VWK*;}&(PFspLn)~q&&3@j0n0NF?QP6Itj|DLX-{L1@43n*pif1Hp=qUP%u~Q zAfjdKq_HFsu^YWvw{Y`G7XxCUlh_#ZR*_jX=mgTxL7wW8$YzP_>MbI6lQ*4=7J<)U zVFs}~ALRwE z*_&8*=#y1Cz{;lUHS|{4`q&2bFGA*-SRALKH)};@s|AfpSzTpm=GuW;$vUptWPy36 z3==E@1JD>udld7Z%t!2SGtUII2fhF&+~vG@f`l-c2QGnr)Z zb;dSnafip{p0*Qh+L@CL`>c|-P3N(}`Q~6W+e#IvNCBp|YJ+ZSFxv4I1xB|Y1QXm( z=cs^;#tY0Nk*{fku+~)0w7o$6R1S<8w6_>Ae|d2*akKim&vJ zdVV$S#z{zS>A+6klN^Xq*0z zP(8!M;%nJs{!688w`g_6q<3fAZM3#`mPEwH^$dvU@JzpNbXv5K7P+D#Nsu%NJCkJT zbLLg|g5Dop=soEto}|QR%q`n8E2oS47B3^f4t+ds(wF^~;#*=%lQ_o6=nNcTI;FBH zyA2bV!!Z}=>(2I=E@xoaiRQl0l9Q8H>u^l1HrN=+4ag0gl}%_{H_;QQ=&n(x^G(!O zf07a0iAnWCwzT zSsL8}<;7Xdhgq}R4c%QNb`k~UGo5@2wqV=3JOvxDfX$X=NHcE0>VPqVS*O<|$3q?n zX}~rg=$Lc*RW)ixyB*$ZM`X-*q>CsMzaJ6}Eo$gW?c_L32V0n?<@(cEj%!s>xmGEc zl+SC~y^m-w?Pga+u^AoYrdo=lTu#-6F>?$~gFl`|^v)i42QzyVYTK9Q{@l+doxmfF zB8CTbP8yGrou(BX=8@k!7>X0ToOMC%SRgcNhog_RQ>i3;!hjt`wsq$yv^d>I>?m^5 zonCT#vw)U_iEfE7B+|JuVk03TOw)yuDE4(+@8SbV%n&b11u&YG_G!z_7=}OUO>V}s z?2%{+{m=F=I5#}2CMM zh3sXcZO2S1`b53p!XCZsPXft;=A6~VaLKkahD$P)i)|2SWlDwuDpc`JiItad=}M|<*?P81VGsio8dn-9OZ zY*ZxOG&4@IXuHumX)G5fCZ$7egQRrOIar{qSoR9K1^if*FLdTmHP9ts$EnPiFBU!5 zaNM%(hIdM3(l4;R;jEugaA=pnsBf8N+A5HKu?A<0(?jkYKUZ23q7(O}4YzWHX&lfF_CJ*V6EL5@=FfVUIX)FbBV4Mc zO)u3XF>%+b8Q>E!3z0=h7Dft_KA#0FMPe};ShJ-x0$94Gg>5mMm2Bw-=L_3mG&% z@7zs)|EIY3ZT|n)@N;W$K7uW(1|nI#e_ZsAq~|Y1$vOZbjOX>n!}%2dTfemyfx((b zt?yMd|9a+6M%qSYLk-}Sjk?*Cz_iyh0F7pqZdTpG2M*ORP&zo*o?|urO}g*BrG4i+ zQ4v0s7%#y$Fj1|qt!b}O}y5I=ik&WQ#Pi|%;tT+H=K_BUer`D z5SZ!H)zHr9;V5`K5?A9U&qbq^a8|(E-MrqSTb@&2yVky|y&eNF9KEvn!V52i4_>^v zzOEqmy5Qb07+tXcAr!`7oLTp3d>8`ENBA9Tzpo8OGlDsKEJ4NkE}J%md|~EIL)-`4 zRfAYY%_Y|WK#FP*Iy>huB{c{~KtG#m{BLC7+)`si*HU2I%NQ3=EXx5APAY;Ns6o&T z7abM%)i8SVG@Rn@^}%kO+<2vh9d4ObD;#u+q%mBl^rUVv}ek~5hl9EEO z+ZkXy?I=al1Xw%RR@>d(Vvqt>s=+$x6&<-Rspn03?QLCe z#n~Snv3RiYK!;qkgMA7)4;aJCXi;vo;w|<{d;L4RIW2zBswY}>@9gf~Wm!xaZSekD zyv_dJy}jPv9aS&2w%y<@P}bQ`TESr0gXP1pN3FRC?q1`Qd1~mor8|x${}6Oqrx0|w z!4x9Npwlazpkqmh5$^P&C+ILIHHN#G){z8_kQgbBjk}m6itDiLDeQ*f-)J4iD?>A> zswDFW`an|pcvF%5@$w)LM+abP*7!Tz@Y@<1gvrHOO-lI#b5uIyRU?*WtE!UIGI3f` zm2^KxOH^IZJ7Up~R3#$k@v1lBKdkLe%@N)`*AnwNj3&&dMKk6p`uLbA&U0s5yL_xY zVudV;mu{I~KK3p-=qtt2!K;T+`B_H!c}Dp#Q4U?6p?OC-X@jfr(7%FR2)jNy9Gp$M zlN&K08V_R^@`NyUVM}NdiIW47*~ehdVfQjCmhJ+?U4mf6m=ZknVGJAr*1X9{3Zugw z6ePi7mXWHODApP`epVHZII9k}s##TyU=~co&NKr?7v5WD-&Ys-&}&vT%5j=SnCGLW zK}m(E2Xd_{r|qMvV%UA{<}A2YRk4jI`zC3;o(Z`m!i$CcxP?UX_#tUf7x@xEe$7pszl zE>GUJWq}J3NWvDD$$}OZ#)T{_RtT7=!{-gl^Ye3mf+DAFYel8p9hWjBuZ2$~n4`hW z7~y+9uQMHu4=9*@=mVcfg#Np;F4x}pE0_b(MZ9@N9~9{ms4>UP9F=cosPU=fY!F~3 z3jb?xbwPh|+p)7PzN!1mN%wI%Nx3L0#yi0t&dh%`=Y^W>lZ^yF{f{`}ql z`uwdA;wl-tcs%IOE)O~SCm($BOQq6WWb={O3xdXw_JwO~8b4+iksK69VM5kIuXcJFOlEj| zHG5_6Rc>4uafJ|0dpInOzo@yZJ^!MHg$4Q&)6V`*iJnr$#qrrkZ+-Fp-+cDz2cN(5 z`_KOP@1K42_l#c8Jx5E0af{=r0&$Cnio%kr@V2UOPgQtFRk*Jzyep)c1XbQfH%-?I zed0|JO?+nb%D{m~Ii=SLhHj)?BIrGn|Vr?;4_Q<`E zT?vM-6L~NUmaP?N!Dt=LF+*#xPj^hS^~TloqQrm2lQZbQ67-RMYq2PaKkSaDe&3~c zk=XhVCJ!3~n$SP7po@kf*BNV;wd(r89KR-hg>QWM{cvHi;juz<)c3EcFgB`SO!{?g zjlOr|lcDFzhCQC&k0Yvy8P3sprnS4y8=r~jVCc|~X5rs_gzO()DxCZIWCB)Rq^puZ zeDV)Ze)<*H&m#6RlkrW`uz@eoG|Dk~Y{G;D+JtpHNORQryR?^h~OhB{0MWE|~ zc2i)QxF9qmPKzalQO3+GC31mD`>@xjimw!3rQs*BjpkQj3Ry_E?I}3TJt6PF2Rtn} z6v~pViS2>hjQiAiI#%9*4gK>O3>Axp-HRzV55If=>3bhPedphv{`5EEJ0QP&`lnw! z{nroibLQ`T{N=mv!7to;rIFJ6=5efm^z9EW#r)a}|xW2~C7%xnkGLCMG9;RIj zo95Uw(IVL(u}WT`{XczjDG8Yr{TxtPm=j=#Ud=g$arC0DhT7$ zJf*^@(sPBy-Q9u&6@{%sFuA&Fcf?7%p@xvIwZHft znqpB!k{VH#E#~_0!QF`uE-7*mbgsy$!6d;_1H=R)2ged1Oc-*yVRTUgBpJ7G)_GtO zdvb;o7bG=chG=GtPA#HpU}~?3ODtx)CWjitU*>yS`pv)r6Nc7P*%C_x_Yk5gVQMR?vByz3l?uI-Te5`|RUnQ>q z$WSl=FT4U;5XLc7|KV4^pwJ2~=&>nb66l?ocH3?jqQ6h{b~EY$OXwcpdh|CXuSxaC zM!Re~M!KYwF1jrv!mM2OU9u$)#A^-Lv#hGN@t4D4ec>E4h90 zSe4w;Elj;VWmBUD2&&{(D1)SOJ@}oRtFKtRfHD`vzhLx8q03A-`dHR*s`5{0a6z zDH`mttO$^SX3W-!$R|c{@7`Cj17SBOX7e^UEwE5A-4+y@lzP=44*lzKFqv&Bz|DpN zd~g*92g~1i5XTW=z&N+4=PZflvcm=)1Hb}K;+=dlvXbp1Y0 zScARQElm?r5&^NxEqe2GvrC7_lu#H|;|B89EvSl>)_Ez|McnCR;zgWi1+BN0*(24e zG01PYaL9tz&qsv-dGky?KJirs7`qoFsHeuW6{Eh_3r3)|i<{Sln=S47)~y>wEvrlb z%W%qTV2`9Go1TY4!am626~eD7f=ncYtxj|)p17s)-gNZ!=qOG>%o#ML2r&55#b)fIQ6kg%x4<;0&3 z(Jc!f0WPEg_%kIr5YB0{!d^Uy*WtQsm`(*FJc=rYSWzKA>JbYy$w296A(FGB`+OpS znbY9T-2^JOsRaMMEg%k(GsN#r@OT!7*)J+#FhHoRU;#B{6^~CV>*AryjA2odx3VtG z>`oE}H!HX|vxLa8SygrwylvShMw@Mp6-}1d}xt=-Nd!LHF752^9s4Yi&aT z5{F^2Q4k{%raHc+UC4%@Tbpeba#bt?`I&{}Sgek7gg;}+@~8ZTy|=0#Rn^s!3>lKM z2Y&#&>b|P#R@JTQTldOcMx*VptJe&>)$r(Xiry-V56u;v?Dzu~N5!;t;(DjT^upYC zURj?mCG<3`X86#&1BkmYu(zWs&FV1yZ4Mo0|9DdmKN65~%akTGenm+?cIR zx4!U=dq^T!CYM4xjE0`E5NOCzdqwA$Y~`8`OQ!rulK_%aRcW^j(1 zAd{XKuZI>ef8CziKz}%{TPuDIu^@e0PseX>6%qh{jyyFqj|bghzcvV%6GGq3(6U(<|PxWTP&) z7K&tj1lT`s9JL>2bIgm(Va?-^TXvmsCCpA9zXlx4qJ~+ll~^W9i;0!VQ1JW2419vS zuY~KvhDNrcak&0eGV6Rx1jo3(zM-Mk`o)b0cQ(KOtoh`U0Ky#VERg2hB|<-?*c9o`27*mPkl5f5eiuOJvfI66fv zIBQY%^omdSu{Zhls~7i_Oct%fGXL}d=hMMa_d$Yb)JlbNf~Hy!%PVHJ=0Y=>Tvq#} z)A>$%JQvM(XY1PKda{z;yY{Itt*zM)*Y@y4Z1=M)XEoSO=Zfg-$^jh4J{`NmtQYFW z9>Usy3AHO#bgncid%URWT!MbgMdWm&f&qYSe9X%|@6bSeW1c`y<6r15N>gEa2*PF< zk27H`0f1b&Y!l5?mu#vg!9e6oh4Mn9Qp8z3hps26aa+8}%A=WFG9jBn4Xhf@U_?T)^1;g%S)R!q=UMszqg>j`e{5lQS*UN{|h zZkdLLe*4S)3=Pp+N9R31e`zPa^v_e9KR+AHPwjFa)$5i^KsG!HM||#r&MKr4ygV<` zEm}~&v&@oc)eDCQ$ih-xyyq%IC)QvI-Dy*N?KK`g&pbQw_#0{~`;FbCP->+AKP&wM z9n-qf>PFfowQcuO`SVgUgVv_ zN`;do>C{jO)T@lWX!*~=b6YH<+pv^6r+$!IJoJ38M$cMH(G|$0FA@*}i-BswHN9L$k=IHav9PK8vYI;5gAKfdn z{=72l-DFnH@aNz&=aqTxd1apKCbMdlcn&_AS7vP+G7GDSeQQJ2K&SyZ-5U5qtVDCp zsiX#>*WFb3?yYSWVF!^u%FF>Y`l}nY3Q3Q}Z z10-ID^E1GAQX4!dm*JO+3FlY*h*~2vjdGzvQkgh88l?CKstr5YN2B$=eTKD4|Ah7eZ zSg$X?F*IZey{A|;i{(Al+TlIL$`C~aydYv^r}PZTBx6~yhBN}dr_xDGq!cB|1PLcd ze>elB{bVAX(KBE(>DeiCW}d^Z)QPOFWk@mxa0ke=o`?=0MGZi-G|~2_(ikQunI`&7 zDz0NxXgv{$=cwE81kgw%vm~z1=;#rfO~RjvK`;UFla+?9x0Kk7mX5$$kquAim^Krs z#q=zR!T({**`C!S_??ZUa%l|GNqa0~7@n>q3st<}pb6ffAIl~}B5)W6yjpvMc3Qbn z{VyD*-&nu=`w`R>(xn)ZO_R&oXbwx_bXBS zz(&1i`;mjgZW?uQ4~oVvT>Aa7lM`>@$9jzSO8SR@%8*TEh*BBqi3)X%W(kRVk;Wl3 zsU@^D=30LNv4&*ne2H#L$~_)od3tYUl8OAZb|9aFe@K7hh;dBaJq}<>hsm&9s&oNM zYZE&2jPI`*he{Qk_gzrz+_`!*L;9@?=zMA31kvfIogvbEdZ~H(i_OO$v_82`%jg9t zd(xVz1A<9Q-`?ST4zh8YYv)^M-*?KbDDPH=I{QJ+fCBH(=UO`}^BlE32)uAGS}N-n@9bb@skh{%mFQ_PZMo z?lf2b7oQ+sJGFIrZR@iu%_o=IKiv1>l-9E80|Kk9l?%;V7pxY(e}3b^3G4dak3dRe zALuz6tvjDLA3R_*Kp=cl4T66B#m18xTPOb3x_7p9avcgc9$aYMJJ(!&XY-wVtrI6( z7aulPulVpupr?DBR^pwg#i+f0rg`>jdC>KF+@2PW`3-w}?N)pJq%?f6o+oHDWQQRp za?3`h2E3>+a?N)>^+olfz0mlrKWVRg&lqEmb>{6IP&OE>r=EK-yuG6wjHrh_{L1E|hiX{*8Ix-wdw1V~?yOO&3=o^{*2h=d zr_L$C@l%pOx_$Lw`^}(a9WlKy)`SZJeBIl-!V**`VO?|qL_P^YHN?o?Ima;Li zE=~x~hLL0%wRQ1odwtyp8Q(4NxE%7ctDB#l_d%mykCbSe|9G@@`CA{fXp-gfVPW=p zG}PAXx1FVO7q-o$+m@9`N8hv&9cz&tU9sQBx>>`?Ie%|9%o?m@~MY zKCApv_Ww4{f9Cwc(dY2~?|S|V1w&&V|Nr4o;MMu>CH%ZP|B+Yczn6FZLzfqG8Fp<# zPar$opx&8iJ~m5z-s0$BiZ2vHr27x<<17@`)yM6%yRF+dTOU7bUA_l)%+~c2?Z2Pg zT3Olph(_YFxBJ#VZ=$aY$Cyi|j*mad$nbD?#=8mH#ZNCebT{vuYyNl|cyC?)9=%^K zT-tbgVe`wsaSynGm(xNF$_%ZXfYC?y?oJ$mSL^zT=B+Q8DP%SB(%sFE)(HPB&7cI^aE1DN*urS|$4)<#R4 z#`hjr$YaB%)U0$;&(G;mI3kbrFg(Pvwy{Ah5TSzHl!h+Ou+H^D&mqTx3rgfL?;_bL zOS;?3?B9Uh_%55==D8JUd-Lw9JBaoHmr@K22SDzIl7|)}z*k4>x{% z)?WFlxpr;i;W;RWGc$lQlI>?7wQrtp-BY>Y}vQ?>Em}XkGjf#t@WFfdx4ENIDmZA3)SG8w<`2n>W`05w?fcxpzTx zeQ?o^V7MV07j$Ry)V=o8$AGTHr3V$hSN1Y#*)d1iCk(~zm2szm45dS#M3ac{x-ez# zZ>?N_wOfLZYH?nsunpfldt>YBEu~b5V#_qav0x}lsb)1jk&or#6eprso0|8(Z9SGp zDy@N1Ach;Ffl~=~$0ajL#&a{;Fy=5A=23vl)~DYofddE}6o5bdZfj+A^U;@Zd<-aY zf`~KfhZ9udj37?P4`)P)Gm1FlemJ8_oH4`+`{9f!aYBd_@xuuzarPpP=7+O)$8lOF zTaCZ;q3Qq8&o<8gcu_TI9$DD-9YB}-Un3)&i^m%{O>#q z|BYFPF|O}fe3L8tU53UY7K)OG{$LdAZ}$G>mVb?ZOJniyWX3H;$;s|`Dw9o1Z&!9RM<%zF{f$@Z zJ~dO}6wHQ57@xLyD`{dRJ)xz!*t~eHlFo$jHAXI;%V5+y5*!P8>M z5}`LU`y)wcs-D0@?S!6XbjG+iY&PJ5UIA@^1Ry~yeU_Gw1cg%iW`JhbrKfgPtFWQi;dqJ0~IX`Xl?C17X^oFwpToa<`f6cI0R+A0Cu zDdCbWV*ZB4A=pC**h#Rl(brpCe@{m9sc9N6(N1qARYj-hG1pt6B_i~2iW#v)E;y#9 z%muXDN)sjyB&%W)?6d*@!V9*`1B|S_uuv+3W@ds`t}zcwsLBTz4TS2z4Zx#MiUttiAjZ8A*`!;K zdX9N+U~?(d8wN3pi^h_796@C4_P#!=sUv@}24a^-cpeV^^aX^5h@ep50}RZecO~Xf zB7y0XeB(#G!w&$g?c?qow@d;!(H+?tU|F`Mr)?SfpPgq%`q+)zC0KcXsMhvLWu=N; zcAQv&9<7h+YWnysHxWxp^z9IlPC_8Ng6?cHg-T3g(ZZCTbi- z`wsvxg@#y{t=0?W3?9+tnpn*+lod0DKf%_>GAz;8`QCa}B^~IrVH&lg#=P6j$B8wT z17vr~`nBWGgT|+F6JT3RYX>Ic$q7jZoX*grvS1UXI?dB0NHkNOZ-7mDP+@-jUIIDmow(FWOGZEiNrp&J2_4)P zbfA+o{yese6{#8v3#DSII||bZVNh+g5DGi$1x&r}>aWy*{gEm?pu;#F>}V|yk#V_^ z2NbwU1KPzsPGnKBv2t10wsxNb%Enp>Ic*#}=xfKzvU*0wQ`k+~h@P6#(h5ocB?Hha zF4{5T2E{rqQd(X#YTmwMZR-td4ODx^;>ZXxy(~YTIs+*((N>T@b($UnuL? z0sx3iv)0n6LaG+%vj^RCpr@bpS)q?vVX82Xmt`!?mRJ=tjCo$Ej}uu2Y=m6q(}?L9 z*w?dKJeks>oo11bA<`&=9kQg{W*TJ>(v(J~cu2Ezs=*Y*=_eRe^7O~O27m+rSF`9} zR2e7A)kDsRO3k4oDwTB{Q9OEwv)Qyhk;`gQXHd&7Rw^49XW^jt81ETegjHEVX`?Wo z#*0}~u9>Tq>IPqRoCp$OdxV00qT$WGvde_i`Q)_n^dC+;pXtK#$tjEhx~CJ#xrCNZ zyAVMEB9Y8m@w5(TSpjZDT3jmq` zfWAY?oH$M~se>`!fh25TSOFsdh-x#bCDLJx?ao1nYJ;1N)Rw@A2STVG^{~i$-T47gTOyg1K9fpkVa<^V?v)8@iHHC&E(1h05#V)Ct6;3P-{qi>d9bjoRse;W z2ZXT-h)M&DvP!UstXnKLu>d1#4oHpB98+O&&_wN-2}k6P6qSdM+&>eJyUR*&SZ*F3 z;P8fxrxpyza_6;KO>A2*D9Js`yZ~mBvs%=J5_Ku1CqQW<$#hzaWL=<94-jsaJee^C z8M*+uAR#XijNnPrmN-D78nJ6sD-A|GIXQ_zG!riLxEC7fVt??-Lmc?mz3}XfgDk74 zWZd05+qpdO6Ta{?%w@)fjCe&raTSe=`paPFJK^Yx3P?9uA%BPq8IyD;pH3!coa&Kb zFCjO-iUTDoAn=JyQTx%ofp@O78A8w#wdw60eQX0aYPEek`mB#$q-GS%Dh)VCZLKt= zo}u?w7>cS?l*);DWFZeEsx|ZA@5yhG?Az8xKG4_K*Kan8=s4C-jt|&1^d%bBSxH^_ zI(H)ni2g?9NTvG63Ze9QFnKpe;3`d9ik}!TLYUcD#%vPCyug`lql`INI$px^{*Hu8 zm7|4nY2FIVrhvA0c^-J#4y4D!bK5<_LSN4lDu<#c_TY?TP=t^~o z95yPpQsSVA4R>t|p8S02uwm8}%4QiFw4*E_PY>|c1{K3B&|#J2kd#NZKX`GFg8xu^ zERun~{-U`|SSlC(6Ol($$40T`G@RI(J~A?k#Ri`@I)O{}3nJ0aF{pjojMMiO^bp~q}tX4uA# zFgXiF#c0W94`LjmciW_1trByoP?r0JGk@fzqJ*IexbT-V@`fQK^fq0dJU)K5)PRduSY9q$ zu2ggb@k3`O5J<`+*sV1%`l&}$!PK(bi0xB$j@ssu7}5zXGxOSC1J;J`E1zYE(bW}{ zmb!U*NIFss*an2i{@41t^4I^f4gb$SA6Z!bSG@lm8yWHDe;N+G^8b7pKd<~h$t(ZQ zU&H&)SgOO*$Sw~cc^dZP#}J){?ieEt_LRqZX~-yvuQgliUyBbmfg%cnd4Jf!J}@j$ zWKuQne%xNa#Ga-J4>AG$Kc=k=Ks3D)C<~Y!Q6R%zMS7vML^R3HmLw41uG~G4pnuU^ zB$ySlUqGgtZ$E(e=A2+{zw|V|Ue)Jt&{FFgF zKhTa3E^Iu#&YmMF5s?`-U{HLWw%cL1xpukr-KFNE57+}GUm|2M=;7`VVUHR+69KEp zO7Al`A~fOh4J*3Dvrk&@`QbV#OvvCUhx^_a&DGCa*ZztR9xtx9&Y$#=pQVYVszV0H zIM%I8XPc{kwUpf3?I|rm2IZ%pfOzhM=93dv2$4?x#`s#!y@PutzF)LHJ*7mnvgeS& zagi@RX#Sto4ji@pSxkGLkEO`q>%73L?*lHs)N~*cp2*OS4h)OjHy+$+eRxNS!tPX< zQJYBSCZ_;9fC8U6-Mn)JMyGx6J0&7bDLWXDbPEule$YI3!3uanwM#8(Gz#W4Y#3=E zGQzd@{^RC5H+pO@eYp33?Y(JtTgQY{i3Fpz-i_%N}fXP=uzED+jmTTXg4;VFx4TC4S zw_9!zC#wkUFxpcOA=>Kg)jC|gdcOMLJ9-z@K;1vN7A7nbw1ao__Vsg6>>B)6WTiOk zQqHowUmnGcuQ+3|R%DEnH@v4`Y80+ax*LU52KWLys-n>3ILi-uU?7 z=!?^k&*~5Yu}>iH=38r5&(hoZK>(;_5mlC_9rgY@2o>;8|3s>_ZV5L0M9}}rrMa$6 zwSmvp_w8}%fB0`r|LaCl&;Q%gq%j6Q|KR#x#Q7f@%bf5>bLC38v+`;n9xP9Y{=d6# zx2gZ{-My!!|8K*ybYdZ0_A{lq<7s@_;Kz^vJd0#cZz_qCTMi>#{@602i1r;j!15M5 zG&*q*GUm)qO((tSsiE`>*hV+)b$0FQJl^goG&DIW6`NaHLf}9i8BUI1^XtU;bgX0e z$P7rnhm$YDJ+o<4%~kVv*4FHkE8M!Od=uR zjFFG%6I4l)Drs7UjRXdw*BB(9+QIWI%IKRj>PUkpgOG7xAGNxJu%rsppZDMrtTvQOg%X!SI%Q^x&Upt;28t<%>+uHHsnJf93il3IM z#Ey(n@H;%~!%)qVjY>_stFS(wa+E-014{N^_2-r=nPZ4WXdO2}|I6zVcQN6T&z!H= z3WRZqusUI7879qdFqLRV7yL?^n$OeO9GsjLOF6%6jGZ{MI#9=2I}B+Hj?C;y6T={$ z5huP`CDzWt)c7D|w?>l~v4*z#8>mG`9$2$I@GHY1Vp}KB|BjV1HuO0`NI1jkgQrq% z0)^6a>x)wONM%nVF;pzMM~6!cAEcpLoh7DU&WANppjqJpdQMjCFy@N^y>8#3U^I{< z$&Cf~az;A|q~wdu3?c=2X4|Ep#0B^GjOy+idEQ)sZ@Mb1G!uQT0pNX=eULdG@Q>a z@X|$$quon*^i_<$L6A=EumO9+gyt%|%hak9iN!RQBnqg@2fV^^KEG5c_3yx8X)L7| z^2KABe5#PE;8uX%RRI1gX0=#*zgDtiAcsNpLqD0Px23s5euca#^XAY56?uZ-0|;i8 zR$1!AF0f_R+L3=R7YoD1xn)Ro?ENSCA$`Ir+3|atoE?8jBc=e}>t0NBXR^Z5UdLI> z?b^LI43W$j28sTBT@evqZxe~L+ZdRR0r4`{ekyLQ0XOB08t~FtBQXf)<$5GaqSz7W7*%%!##rr98H;^K{6DjVvaEw=@^^E`|=pXDu&Y&2T}*~`4O^Pr-sE2AlJ*tB~Z8MkKu!8tMV%; zy4}RuaYr?9q_5Z#g0mDOH0{?Rq7B|DuM~pPzMO(I@JP1&#aJ>?{o>cPPi|DN ze6{-M^2V7YIPSx_3kz3{OF(q;S0Enq7jgv*F-B@g z3 zdG6dPzpoT2H^a-ftx{S>@Y=nfdGHsKAm=mrvQKa7<9-1`_M@pEAo0ldUi5|IFg$e`>ht*_#hSw^$5S`H`RltgCaD!Se!@3p4QNyW1#pF*0O`yGoP(*uw zaF611{KK*fld^=YP&x94zT3hRVaHX`wq5(L zOBE>=rttL~w&zM3>`rfy{1NxG4Ot{cWIT_tZ@4@(FZ~^|mq^uO5>PT)yp&6Rc{yJR zgp>92e#NKXO)$pRq?9LHlc;1)Jyd~%B~p^~r(cL6C4VWuLNNxi2>Gj%oQzGuM@1MB z=7dC|yi%_Ci|8d1;L1QOGAy=oU|lRJ_BINAY(*>dYOnv>A4Kz($LKG;Sut2*8uIB zwicn!0tD-~95;{j-<$-wOfyC!_X5$GUztTOY|-;ifJ|Kp18k>@{(D>$M4)icB(u4P7r_I~QKi!~827FwDzy zqEaVdFq12kV^X8-I=wFx5ka7()Gx7BwXHI1iwrs&Q^r8MFLEUkiIHd90}1oqS=l3Y z^@k_uJwh~bdE7YnHe}bhd_jzXp+xOzWE}?Mu`WHuq(Eg|@*R)RwT#Xt5HS;=nOY=i zIYLv?Rv?$7si0?LA!-#-0(ki-Vzni`2kOyefZgkYt+6#|#CR3b8ewVeZs94XT5u(TKuA3@4Y`l9lSM?{erdnU6>A`7 zwf54lF*v?zr*kn!rXjX-F%3}=&;WKGnSnnN2^tk4j*q+0drN0Y=mGL_${)g0%^00F z%N~qG`gtIb3}kk#)I)1%0da1>gf>cS*RkeXH+VQ+892!-71$UUkHR5Q2Z%hTwU|fS zK0cN~)JdtDb&#wCA8B(oV~)M!^z$-ouIbja-;fsHINe%75VH2sGw$j z!iI-Al$}yn7`tTxaP`dj)kl}BXU`y^cjM6q>vwLF+h<%NgIgqDzEit;W9=`0Kx2oC zhF34wkobn*to`}VtM@O|-g`g_%TlX%67~0h)O2Sqdlc<>EC@I&wqzEYEETiMa}|j* zszPY4Oa?d2!WqR-TXBG7=Z5lHE)=on-tP}0ZFlP*aqHJ9kDko^g56;0MmHT{yl>{HjN(^E#*lk@=u)#EMZh6ATGy*XL;8ZhPkK`oVu25a1 z>WHHpeHjYaajhL4MFJ8KfhemJfzgFxKFcZ^)(pMR+*ClPQ?akuN5C0@Xva3YqWi)g zN2@xg*|Zu-=TxdW3pFlY*sNl_F#$|6G%3~X_9n&7WG%0on*e*n7&HW=ul0(WiOg;( zusi4Sex?+Q$Pz=WNCF1xK&k??TP#VW9{2F+WX`pOrCz*p9HL#ta-GLB<#GSjihHf* zIt%!_X*M zAC)dx_f?ykZgG>&oKgQz)XBY>1%@-MC_L9DPzWYkT#AJV51XPF7p+JP!k`gCA7tgW zdjki&UiV(m04)S^7EOgk>y`pBPVB=JJ-KL*vd0TIK|R7?nk%7e^YWZaI5#IpEl%7{ zX)Envi`*&T0}wheA>c=l@7{jcqd|0rMf!cj%+_qi%aN;TdK|7&Z8a5;~- zd?clQfymUA@gl?x*FNEMTK!H~2iP98OHioS9gFlJ3GPD8>R3nZC{;LqqIcM=oXBV}+UF2Gp zxH3hzpOd*#WjT}gY`3!L7EZHPn0?=+2you(%xO|wo9g$d<0gfH)iwD_mh0=VpTjcU z>r|oCVeVj9OHYqZ%uFW~lZ<=`tKad(;a4tsVn}xx9M>hhowO=fC1fj-c^$y)OQ4TC zoghGp!*Psv!{r+$flz%Okg>1PiV0gIObGy+hk8J$ zcy%)bX_Zo1cls2l{s-3LPCp)1K@q3#rys37eByOr@4M zQD}GX18+2iF#T<~fixj1uw`6vtgVn=bnL>-8@plcO-F&NSbWyTKbmWEqC%b zdHP7>;zDo8)wVg?ZM|{#Wxb~tf`Aee>CkkgB>7c3Zvc+lE&y@}s8u>uKNDxpWCkEE z6nlfGKn@_FbQNLrD5eBn{`sCRMik_vT687O=_xhXLv5dF}S$fB>>~! zF-CX?F!cZxu?y*mHtOx%#wn$J4U^wCq1kw*M$6zLtYpivg#Mc<5Y3l1|m=1fHz@ z$nOjM4X7R!;X5td2Gti!`U|G-$?t?p!%tx4UD`N&MV*z8Rpqh)2dQT4%h&0t=Nk$H z{8~Ohk!1LW>|4s=Df`lpy6uEiw}R|~RHr(tfB}+dNdL5T1`9w&6b!i_mgXAL&CJv6 zM)ib#qeBe~lmwJG!2=@PcIl+`Z1odS{v&T1Oaq1w`RdF6)zi1TyOsZI8=m^+Uyf}Z z{SY`<{_pAA+q=i`|Lon<(~|$U;c5AQdM*FYZIAyS?I<`Pxc}^OE~~t#4DmEMuDJfz zc>Y@%B|qRC+@=HNUh~tkFDAsSCv6ioVSITJsSn-#a>RQ%$i6Nlnv&g4aydr7wG{Uv z#pTDMC6lLdKuGPJpsYy#LC0nyazwux!C#53?UXoJK=!v-($tBAG7oZEqwT6q*$6Q5NwvP1ZBjmQUQywRaTg^FaznA ztU;_GpeXR*(PFvMZs!4_fWD>BCP=UxGEWBmvAPKl6NXeJ7-{618~K@K@#y3R=_d0^ z4wYaxISG?gx&B-R36(0-9G*Z#oKB8UO^l8v#3>SxQ6ZC`t`wJ&g;EhN*ouQ3>CU(l zA`<9?NX z(Q0uWrg_V~o(Yw?g5;Gi4_0{S$5ihH&3~UEll|t`JMpO1d*ZfBoV1t{5X`)>iAuG# zsg4>_4{VdAib~o-V%;(XG~N*@?T{FcIvT0l_!}RBeNhtP~*U9mBMbg7N6amIZbkc-EGLG$ght*gnNt9G{p; zj}6WYy%1$#NW!}<#4!V)#9~oG1lR(dSkkPBfKLn5sb{2AT!ezO0~p(JB`WH_Pfv_P zzWDS=5)EuBiEud$Y8J`PAO|uOc^`7+)=6)ZvLaoX3s#-ZM1O- z1`N@#m7jIT-8R81xP=C^7j-mY?RIfV?lt_-@)So&P+I_#cgg`mh-MWC5I3j3jCT#j^BWCZUBiU7nyyY8CZrrB+L$rxSc~2<4C|9pJ@-SN4iPt zat|eUZE(;M-dS>+GdzWywKzQ*kt&ck?V<5aB6>dsoRQTN5`{^N#1l)7C_&5e53D>r z;Nv2u6ir0SKq|*gkrIxw$SK^7Oo%5EswFHVIP82y$?by!(K2X~^-Zg$nR#H6Za>5HQ>&L(TLmKr8uW|Z=BHYd3TfA0=W z!{`Lhrp128NLIGiIO~b%qsnKi1%$5;s(=O5GQn!$Y&3}+hXtWLDq*2jrr5gZMy6PP zMrtJ=6|T)4RnUkGm0mKo5ReMdH0*ji9fn=_zzn-KPzo(-HDzif-IU*;jvC~uHsEyY zl`b%m(F>k^z|+Z-gR~z)SV+WX?j!syu&B_?DKj*K^Mu*5-NAE=g`$G9+7UQ3wN9Aj z(A^1TQR%=!yI^9^Fy1-`?Lfc9@?!0>Iw6*LPp*?gOvKVbo@Gwm-V8h*lj+i64R{2> zaaL@6Ce+2l79dDwW{Sy99|br6%A8&Q!gdKcNekXlmA%v4IJFIOhZU}^MSDUVlw!4$e`+@_ zRp0)+_CDm$TzmXp^~(1QQuW4dl0Zoh=mgn?FnpM1TL1jj>f4`^tV^|b?^RDfuD<~)jxcKfZ$X{ z7phn8uK(tT^&fw%p1%i~jc$I4nn0gcA6;HQeG!3LyL@ls{W}|f_<*+AMeMx9ho3ry z86GeV#d@+m_1S-p<%;#Y|AzVh?&;oZ#DCh|y|;D$Z#$lr{l{zBf40Z}S7IKK+<%+n z(rdA`{Bzcy&lP-cCW%lzIFn3|4gMlMK09_0O$6k)*|FnbuE>rQ_@9lN4m8UFc-M-J z>2-hu^=M&I#*yLP_AI97WxH35StaTE@8U7!qDWS zIYyiU>>cws-~hO2`WU8%^9xb;8VHJb`TOdX_ec)s)h8cUzy1KW_zMrJKfYPJ{dw)< zCy?a%{7tmelH}U*?uz#Cwd;3?SaNWs;^UKBEWMR8Cs|5?D5pMUDV~0;y;CT5+H3ns zP{2VD97nUh$XN(1EQX=aN zfkG#oAD1iZ343#r1r#G6+I8_;9+2vuNQlAZoH+C1WHLR1Kp<{`AW5Ep>ikY3jT_Zk zLNr~K?F||%%L@hdd#_7yLW)|P42B^%&k)$M0niczi?$4$80cspvC534%CDdY?Y=|@d z;!-|S@yljfvS0@w>`EpI2ifXu>qAe=Rl!-edg2TrV!tjf85!=C+$4cG6(+vAvC4g$ z>cp#LxOvuaDlwnBu__5>NDe_gDaVA=!+cF7|0kqPQOaVe@-%`2m%AYlA&_iCWfLK_ z=#?$W&XDwm6E>e-z5iA1;<>el4~WE8J#(x2!@ITjzXPktA5Ygly|sS#7A=5fIP?{j zS$}fQ5O%o73^+NL=?s%G``OsM!cc5lwsTvj?>c@1Tj5#XF>uqvbN?mk}>FNNxdUkXsJ#n}h^WCi3M5_G6_;hM|1~D(@s%me>NnF|-hA&${ zN|cG!W&>)Nm$qCYSUA(Mu3}o7u4RV!xXPzx?_KA57ULk(pm&UWKC`J98yv!Z&-!|D z-h+z>cYK0)RC92Q>SdjZM(i( zgs#4FOK@#e>Sg;Tx$)|%9br3|9GspGVzIVefO~LBsT=u8rxX!d7P$>!NnU+RIzs8h z8O)g%jLV0iv38g4WY`<|O*Vx_Ef;!~dizCZ2k5JWFnPcz%Pn1eE}`(faTVxTm+CEq)q4|E(hiY>5B=?!9LI&%Ql< zt@uyd@wCo=z1I2f7M}mwgTd;tpqwc(`8Fmz-Nn|bJGEPVzng!4Yj}Rk=coPL(q_}W zW&dCEyWwU!W`Y37SivVQnA>lsX|u0JDoQjlvna#4E^e9% zq9S%m_va*_W+$vx4Ua|GWkms-HfxksPVR|&*7v;#E}d1w2-Pn;(7U~2i-^oh2owGG za*|7|DCrur&3M$+H8U|hkyw5FLG|K$8>c^7`}CGs-Kx0Azu;~O{Od|{Ny1*S3(PL^ zaM)AS-widT85l6RxYbol*Ba4ABji6dY&up4*gCv;E#wl}g;ysat?2-Ro~yOMD)u!W zQ8Cs0e+@Sl4bM|9$6tKS#&w*l5sbxdbe&UpC4a!AW81c^iOn;yor!JRnb_9Ewrx#p z+nU%uoBy}F&tC0aU!9vi-B16j-l__ekx*B2U6RhV>Ec>#>*X7A`Xl(~k^gZF_~3=# zgQ6du4b36`wXPBKzceYD|KPnW+JTGw;Bbjm}da@$lE6S=+%E^iJ{TF;G-J_wTA_X zR7oU1Tf3cG+&estsz14*W*~Ov8b2OHUPDNK%Jd@4Kj4MtY zK0fCLuQWpZzO10~wK1GH3bgiC;V&}cvb2^6&NOUcJ&+3mKkLQJ9&Cf)t#QJM(j$WTpToGhLX)$x?+r6L{Sz1BpI7;n#@{B}f#+|w4)2J#pqq3*AVEf)%D8A*Ro#jS-)!64o+ z(R*>w9=|}}$etB2kk0ktQk$mv)AIm$JUQ3+yC$^kDy^FVoXBnwcwx@I1XCCNpaJiCiXB;-AS&xlGnkr~p!I zR;V1vo3AC9p~abS$8Q$X9>MWr&D;X3VQ$vi(EH*-ekbCkSlx(_Lkw(aPB2%F!hlM+ zZi{(F{!?Y++){&8mc-X&M=42U`dGB^!%yJo!uUTkkI zbO7546=Z*OJH5SR-OGUm}F#iyifwI5N`1TM3YFlI~< z9tqNgh9Hs!ZxK5G+A#Ex-SmO4r5mKo+s|}lM+7>ZtYA6ZaitO`zl!6`Ln_4K5D^6- zSHeGVFPZ0?2*tXBjWF2N7;Ex=EsBS1K|v(EI|`L{2~}Kq@E%Uv%3%JH`;qZosWpr0 zMNBSsmO;S ze+m6=Ro3gY^{d!!>;5`?TfcO#_}(k+^(lS&av$ouS^0Z1S1QqTtvd6m=>5EX9_(I$ z&7?Nt9}TC*-1EHh7uzBDd3u>CyJo9Rm{;rb?R=dp&MLR7+3}5CIR=8JJf~L*T4qGM z`{C+5?mC`^>OTXyD`nd_SDGlO5>kbOriIN;7K&m>W%r)=xI*KnsPNl~Kb5RFId1zB z&;ea$=%iTxhdke}zX7L=qCD;$An=qxgE^mWl}n%no}%xaP99_ufId&{Plcw_Rsdrf8(a zuw&!rSCg0X!HX1<1`jwKwF(;=!lt&mAekU_v;dHXy-uJJuMdziq893DNlmQ1CdHZ* zv^ZPq-Qa^-rArgbm5eMb4Bm~YZQ}Fr3NXkU4AqK@er$D}jygzAcN^)iP*!tKv3p6+ z&{+`$-o~S;dT4-Wc0e;k>SSAZf@CKYq5wnBQW%`bxMy8r`?J7 zY_OT`!pWZ~Q}Z;LTeoKvKW}J#u=9?cr@c2pSJd(Yb*XZ_V}_i@4zVMPb&p=i1YSs6 zA5mNmcGpK?@d;f#J?*BS=WHVL)SBUYrQk?;+}-kwjK-MzE-oR4xQtKt5qiUg7s$)Q z!CT@yj%P``aMpY^Qw3BJL_fFf^If{5TX&+-4LM|IF8j;_w~G%(tF6b&&OmPhS%Oc> zb#wHmPc$gHXeRVrJPuFlUo}qS5&Fu)xpL_ljc6g&=*6~)5uCnK{Gj{JZZ+F?!MH_C zFnDjy9BTs(LQvC>f?%E5agamV{^0bW$;graWE)!+EPjkS;S~IG_^gIs`X{9B?@X!g`{3|7O^8;3 z`1SsH=>|XadMCpmmj;<{`NN#PlNxk(>n)mAVkrlur*q7EtY}7=;NPE+v*BcP6dyg%X!H% znqht5WNK09?U(=g8AG`M5OD26XB+%foEa(-b zO>(V2K&sV~wwF)n=|~Ej279boCT2Ev&rh^z`t7q@uh@kx@|~xV*r9rPv_^aB4QxS+ zLqQSqH|FS8{sKZ6hawc_3ZIeH=pHtjMl)a(4SyC9apsc|5=H3j|4N_;Vx8=CYnG{m zYD+uj8(^Bb_i}w}j8~HZKcv}DB0Rez%gF>3`{&^0c#8Plin6(${?6bw>gXUxrMibP zbe@#A0%Qt}tWDU1^DFR0x;OvF zWAE_FsxJ>hOm%yOX76U;t8U=qaBZc+m&TrrkDO+(6O7fth789?r_rWFv^WMEyvU#C z1XW>{M8@P=vN3fia)-ZZMPKVRr?ewwkWutTny)YBItk58-X!t4|4IhTMFB zJ8X89U@?9s%}gy}_Ff7Bb=+u1;>o`CC_ZJn1tDbk(aG^LMZQ$VdHwd%6 z&j&P$r$fpk^K^}#Kk}}9cBg8$X9NtvN23&-XXWz_slluJd2xhv6LT|5*oH~GG^OGxIkg_TvQe4Mo1^7kK-&n;!hT!z$@bcL zMy(+ccj4C|+Ea@^AXs4E3lt9qzEYq8A@d<%MuC%~7M1O&qIMs&RSvR&o7tBI~fn zcR3!AR|g@6CXFz!L8>?rc2}c+TC9o)yM!?$;NDixud1z8fq^ZZQGC$ej?H#-fO1&s z!(YOQ-ik#qaTU0@%=}X8M2znNV`%krem9if%xOQxMrDXf%)l|QewJO!&lK0D)r%O? z;>T0_F%HsH8^smOrm>eB%vX$eXeVa~p6`RnyNk!I(*ELOImY2ii84)5N z94pp?^`A($At|}=vfFVP4lciy%{l26FrXsLKmPV`2sLla&^MM7PKB2`i)A@@t!018 zm}!@DPO@)M6U_#w{I3+}xgJ71g{+9S@9VxVTOv0U-O=iyO9s#=+%u$ zR1X9shpupOTCxh1y)c@|j73%}v1wR>bC!leDD3M(+SD-pJ)>ZX%#9`}l0zIM6gyha zENF&2nnP=lldg{?J)UBTt)MY+cBou**Nc{tHUv?P*#LPkpwKIo|8(OG|K z8rbsoh+T;+C{{oRllg5Tn@|SpoSzo`1zq3pvjtJ%TtFpcN3sOpy1?b4tnpM^WwRii zHx|ZJsiSRsE5nCqRGGXjAu+JBKnWtjI!mR8S>v)>sR2UHK`4fjz`um|ba~ghoykA$ zUH89ue*V0iC$>ssR%Q0ret*GYMv9zGinGSvIkZ&QxYqxEjst3EEF2psIVmF+DaJox zPWX&|VLPvB%4+iw0vf=`>$^}cjJWvksIY&h6^9yI>6?IRp0o6`83)KhqO`d`!^jM6 zg8aKmS_dPR(zVq_56mSGP?NW=@&OBU_H?sl6>ga4bYRoJm|WoAojqPt^@qq;+>+{2 zTrJV-t0VrgZbG&d6huKjJRksN2m2Df>BXWo!%CI|YNwrsR9SyA2-tCl1P;}XEs{e-1?2`%^}1wQ z=bYsvCmvV379^3HSh}yHtlsX0ZTW+*pYSMdBlrPf_c`|XIFrw8pdbb8eK9zgq+G`H zs?bnef_GA*09#_x4GJl1t57bK$$?={%vqA%2uXMKDgd+2q=Xv|M8A^fwsUCG} zHPpjARBZZTObljs0^liSlNcR)Qy02t$EBs@O*UtdV{_`vJ*>&C87SA{|f z1@Y-xY&JBV^MtwwU5}1F+BO7ii&2o=jUFEBWV_!YG~<~EB{W7WMR)4#%X-1=5|`06 zJqqac)6|A^YX#QXec%3OEV3NQ9yd>tP0{@%&Dn*ZOD1duIRnLC6|ojuts-ctz6>u0 z9#oyxSDxWPMu#H80M-J8=1Z{j*qh}qb<_in#DYz)2q$=VHe`?7kxR461* zII@KkBZo29B#mA8WNnOBx>vWuss+GXfV4jTSwVTQq~>IV!v5X=>AMYYH~%@)2M{K` z^?!SMe8PJc`evD*g<20Rw}21Y-xm*GrQ{&-Xu<-@o5XJLD)OQx-Po|${ESqlbbt=o z9!Dc#b>B(bubU@hpJ;USfGZqk(Z=xikaKz`!f@!Q$EWPz3EvO>N_>5JGk7O z(G{d44sa{s?0`w~)F!CqCi46vi)mEGC6UHNU(L)`bNqS?8;Ze@PLU;A4PyK5HLNDL z%!It*RGWe`OG$>)wL}7PUN1-@PWMq$2Gk!+=`PplGS5>Jm~-JDVO~0AHjW=@-QLg5 z`@J@U3ojot%&uK-1-Q5RUZ{>Zcfjp$?I-tJ`#A)re)Q_y>YIel8GNMONw>paG@up? zz?7=KR_|%l`Vda5vRMp1vqzo(%dW`(@NU=ShT}naSY?w#BrB}bl0nH>k)LEP_oyOA zi=_vX1HUz+LPeb}^7qt?;$nZD7*y`U{i07m@vr0^m!DZwa`WoHHFNW10Edq^DN1Lb z476wU^?4yl=+5v%XC;`Tkd+nY$HNx|JD7`>6}5Eo86#W_r84`w*8LGb$`PPQ$%c5E zpQ~H#dxr&4+Vvi=T|UHw{SWxAez~`F{-|KJ(xXyxglPH8qruYJ1kVmS7Hu;$*|^MY z%^$KzpNJ@fKn9tZfcCXVSE;w9Uyvj@dL}k@9if+Z7Pl$s5N_9992tpox?8XFiM&6Y z%>459y3!n2 z>UB#BCc-qQp&<12?;!5wTZ-3dR@m7WfOtsg%sOX~IL#-rKZMKfp>-#BqK@RwPZyw>lxc>OQREfPJ==CoxthkH) zx&X*2@89%wsC#)=eV!te=*f4eLB&ov#1OBm@-!-Naz04EbbtMQ&ygSf?!jNY3{Tte zF-jRqu0We>>r@Vk>Ub_nV!zgA9Haa`52>*xJ@MlNHG7?tAQ;G6pE)2RM_d}RP!=*4 zx^yimmnN3Dg$iI!`dfaiC`C9CC|SwIdA8LuB3YK}i}wfOXc}f z)uy#=I6w{=I_>1-bKd3Z$BG(MQ&b5Swj%5Aj0&d0qZLhP{eq39UFP}oZfKdAzA-b5 z{EL8-G86nMCs?Dwa1j7$)`!F>2h|PQGm7;7w%s(8r8pa;GekabtO;F8lZ;fH>kpC{ z*!IY-w5xJi{UupsIAx1ZAV>4h@5lqmc8HZ0zENVl35KBQCR{P%RhBkLvM9`mqDDdZ z+oARghY7NC&J@NVAQL9!=MOb0GE3Iar%@;5@{}7)kr!=7u)PuE#pLr!YK3Xm|H)u z7=nv;xASe(k{hl6CU|f#SqgFO$Q}tEkde%Jm3%)Cnd{^_2Bmpke+{PH--x^jVElx)6t9ny~2>TuJv?>s}INjmIhn1=Y>2IN1Vh_!#MSpK0e8`` zB%?Bpo$&f%z7a`#oMqxvA%BSSK9iK&S9C+ z=M|%}nM$TA_$?)YZzwguw{Ap~MLymE0?)^0w6=#&Gs5l7Qa8AQ%SCxBVY>*P&Z$>4 zsGD^=3a`P=aU2(!5U$#|emq|LLwvM^6(ZEINe`2p72*3^akn1swtEHy6t%9Rk1IGU zQDT0}E@B(d`v~Fl&o&LGjX|E9hZ`@1HRycGei`Tle?s2{yM}NrRMbDP=8XnnaIjgt z?)^Mk>Xl4RK8|M!iIf76t@2A%@Ae|PGE2Y#ctq50_v#8b6FE`bkz~JgqvH1=IGY>@ zi!6{EYRGFT*;^3EM01)0q{_!=ZV8HMkNM*X9MLYSgm|#2!}CW6$FMM%Ece)?sUg@c zCHY4}ne}Y7!UxZ>dux21$a(*WvN>bN%+OTDd`UwBc8*Jc>Ledq`mg?*8)m6^c z$Y*cQ_BROH`yU7j{02dL8M}a`A;49W4`ivQr!qqkI(iww^nNfSdkGFCmf9ZA!ly@KkA1B=g{XkXsp#eY ziQm7MP$;tu(6;9D&`{U=5$E3N&KWV!J}PRT#-!M;NQ>@*uK{RG4?lAksXSbN98MkG z>?ejgJ&Rmk{TO{5`736mTNx%eg}72sR+!~{Ih%iHyeyYA+k`-vt+^tE@4EDXwzg{yX--&o5#~qn;)f7~M3Y+yAVrQhvtq78m-6BS-kKe%Ef? zuFRu1opXB0fW0w^L#t?M<{%9l%f^{VcSJJP);3YtEltsc9b+AzgEdo6Sl-w_&*x<@ zVmVkNOefidK>%tBb8rtCEx-RnT~G)@0TJ%my$6Jp;zR|Y?8zN!#}9dL`;2vl2#{(u zs$ehF1iN3dxN&sgh9q?;}3*5cvo``-i1MqBjF*t9#u{bTHIb0r}vR8E&)B7{DDJo z6y_!qIBx_-GR4twRd&z8w-M#_BEz7h9(cvZGR|c66BWj2OOfZ7QNtWMc?LNaQ&yu@ z+DMo9IjUI)W=Tx7(mHrC-NQXMjIm(qh9#j=M+9E!uxKjYZtKj3w0clxu;nVcccYr? z6wA1RevU^?b_i`(!yS%!vt8q7t*{a{ZF_+EC;c=>?NKly0*#Q;Vb;`pXJ`jGZy07z ziB;L-F5(WEzzLqUAFHG4ceccJ+l>IRRC{H<(zb|p7{06ZL|&)U;ETTQt|Yu972kw5S$vU`?iXp$1*t>Y1pY}VX3UyqhFJPIi` z)kzHdG1k3+m?H;6R(UoTkzr|Rbj}3}*QpV}w8Y#x8&1e%L?8Hb;vsk=^?i^1ZIK$t zn0upf4)~DFh57(`>3sQb+;gx1Z+c_zB3=RQJG-!{!1t$zrw5$p-VX1lmP6qGBh|-% zCHFBol#ZxY0K)6Esx1o<9egFv`uUBbiReFzt5uT{CxVD4Nz=EVPHZ8OCJcSr zduvB~BK5?x?1{@k6txxIK*B@c0sC(cO?M+TK zv8b>hqi}kMUS?rDGcRFQ(+P1NEXce%L=4(b)7PiN>(TCUS}LCHYH(wzG0n>VD?7PV zu`>h}ze#~{zH+ZQa1o*!!{m|Hb>BsvtY zJ`KGpYLxe#$=F!QVyeWU5((VRWI;HU3DBzdV2zT+y`)WDSGeUiWP>M^}epT%4^DC13+7OZ_p}X z*Hh)u#6AP-zLj0ie30@Fl*!>#J9Vufs})gh*Iqp$SZ zn0TbvrVLeoI!h6*h->N5#`$D-(}?Co7ONC>Wkr}STU&6gsw`YZVgcQv!~r@omj01^ zGr04+z##kqbh1R}H|PtHRTKb1H^K)E<|2}$$OqNXeit8v!;99`hdK+9WJ_R}m9Y~Y zyfywQj38xBjkT56hjR@th7KT=iy@~>v#ay_Dfs~(oCMe*#LNCkipks9MB$%B_(yo1 zIf5tdk-vFw5DQ=;B_c zEWv-Mo!udC>Q7ODC}${pD;4hgf%S@D^`BneL_Da+n|^l+KJeFwFa7t6&9|+_E)MVp zFCd!51vdu|Q1crF_!cGhnWxDt!Dt(n*mr7t-)VZ>K9NEWd+yXOM<5eJaMY^*0DsMK z7Qf(HAdov9Bi?qR8$#c5Cx%XKEHQ;sBjFlF-m{9x)oh6flS7cTmW&!D81+W2D~a_+ zy+`hO621hLapebZGJ>)xlchmX;u10DFlaAKY?uGQ`KRS(He&6m!dFAZE1_GAszHN; zf3sX{Tlhv~c9rQk)6q?rb;x8{n&h^k=ats}GbTN^;(u(Da0&|Qk&;I~u9fQ^t!}S& zw>>n?KX#iXl4XtN071_tr^*wdjh7p<$Gsm-+{FT(JVHn0l;VbGWxWPxDkjL1F=W~*mySg+W} zapT+MmfdQ=L6t*vCV^gypkrx9Z4S>_Zz;D<_Cvqwy_3qwTyw;DcN_1f3NBEA*!7h$ zQnAs8h@??zR9kf$X`h{qX(8=pi-Jft|aCSwA;+mQn6DrP{Dy zMi0wRZvjHy;kgrp^{yB~S%4}gUP!H_!#;30JL_^j%iRMO7w1*t3=c^)$P?$)R|Ewa zM%t3?I8QFe9HzdK5!YoE@rV?7OA^0zEbo&U&HWf{lP1K))$S7wL&gmijh!G{U_dS4A|NQliikRApFFiuP`mbJ}bx4+FRHBQ~Lelc!K# zV*-iTYG+xid##A-LP^SgWzdYN8yYkQ21o&E%B3n+(iR>}Q_WGTW#e>0OAOmc9FM<_ISa3EWa!tk!L}tKl$L6DJCwE z2jOWS5)^|8h|psdL@j+3T_@hNR^;MTu=RWJH?-!nhYLZB9~T$yS%oB!_i?;90B2mV zUwt;Lme#A90`qW)XtE)v4twGQ`S}uh|>F5w}Q;~t=i%vF-sn)^T?*TSNAe_ zppm$<nfy4F-kl6TPUkK!iS8ot(o5u~KE7kMa``|~myfY+`d z)*)+hap_8-erld%XZ8t2b46`A@@h4tc}}?7a~vZ9V!cSCv6X>7}Z z0<0{x|L@rNov1v|^7m3?&&>!1!Q4ye9YrqyUS3Kz<^S{F^45O+PwpQE+Lrt3qybe9 zu;&u}nKV8$$U`2KH50|W$=POMECmx3{YJ|0nkUc`Do!iYM;$z^!r)BOO8Zp_5H+P` z>9qIqJ)&;><&H&7XoHIBP@uag1E^(>q~;qB3d;w_=CF-t_7qCE1ntZ*WXC9tl$eIy z_SJtpi|BTVG?>r4B_K(1Q{`YZ#&^x z7i4QV8o2IZVNwVnf&HVi& zCIQDsv~1QPj$WdC!BQ=nbK*pq1*qfpcH!Z*8gUa>a#FZ!4-_Bf_V3*r~J}K(%H+vwC zw7^N^TjzIT_V{2-l2l1F@XfU^0~HJ@Fm$t&(|g60V?)h9J!|^r&r7S+8UzK>k)o2fBki* zM(~ZD2QFk4OkZE(1~b`(){Ar58B+Ze5}yzVL%sRD?&7XtwZK<9&WEL!9|g6KuCGD7 zwZ9AqZ*#wAhIqD)o2~ml$To5nh$sx~K;9bj9LNtzi1^_n9-UD@aBp5uc{MHmZ{BP` z-jr{M9)GPKw_c|ss2q}2@zYxSd3&RQ{-&@>^Z{RuUax+)7b?u_|0ww1HG+?b^BJTz z;Xg_vXWN^NE8e|Xh?DUg?HsY5a6%X?FWVI{Jblz-J6@G8VW8)>c^f~y{Wyz(VL2a( z)Z)PFjqVL@;CJuTf5lUO7v_OFZ~jS-{u*z@O7RA$4k+TmggZ$d7t+bY ze@Gy;!&Y_9d;Zf^_Y(AV05xS4+SmVK0nsAfqfNzd)~IbFF}XF`V!dfCV}4b=2Zt|JD{-HPV1@Ztkszy_X)^U;e)) zfcEg8z=!+HzF<5vvBhDgl{tjUN2tbvgVxpdaNqG1si=&q+$9y_8%-r?OCFp+y(*`` zSDh_BynP_*qu*1=jX&VY>;`z%qyAO<{gWTP!@$;LAYU}--nf5wrv1_>;wO`0;9ok& z8da_7LxG;(>2?|75-1(u3w(P5$^;b9^K6V4>TakQ?YwcugNC$!(14$BQl7Y2f#YWH;lhJ$}yetAfnM1}{+I zw-iU}_0rLo*7S5Pf&)y7uRLSoArern?eVA?u{nYR7J!z?!{Lkc+r`!z7Ht-sA%# z+{*&ZBd(ajJ*Lp!c5|=W;*<^a>JMwie{S-+mrkbdhGuZM8Fj*~F3%V){^mmAie&K$ zdrO>aUnQL}A;{XQSz+v986Tsen@xp(nLOT53eTf|+4*=Tbde4#$7`$jvBuy;!a8}< zMis5%x~Ij!8kr;Na7e&m5)p26ww~;SMMiDHc(v!JbGd@A5>-AGk?HP z#S;7>qUu0Vo^k6UnhayemhxYjf$La{dZ(|nIYC%P*2c6A)DhzDq+zbhyGC7(Y~USY zUT*5aYtpDn@4_m|rNd}|E6UX-Kd=J#dEqU#u2rSUMqA?QDuf!wW7bmoZFIEL5C@VD zU!s9!%t6MO4g=$P`)}X_e4ta{f&n(LHi25av*|K-%?I~~dCNixX6oSV4b;VuY!=+A zSukF+M&l3r8rb;;V{b%@#VXXx#eIZ_4*jwP$%Y7 zFe=+|K4SYobh1$Ss8uM6S{JaHRRTicBPY($Xc3gMaryIr(ebu-o;&CEIF|{;i=ELK za7lrhXb=7Yi)wLFXF9OC)iy%KBd04rqf|rATRB1A#+ zLWm|hZ0@`}QLIRFGYx|HDDzIG0_q#E3lucaIRRTqVWsTAMJ$(cdiWZ4nMF#TI$-eiqGk0whYlIL zU7J%n=)qztzNzgCT};$A9+#>XNnQO(z!oYHL5w{+z!`Ej8{G7#+eXJq}Q|Sv?+M57k$@9njxl zf@;0rV=vL~0CZw18a)*#8Fb%!F-d>C@|0k@cKI0GQ7Z5+%Hibp4zR#S(o`b?L z9u0$#(Q=bq;7OAl5Te-5$iyX-k5l;2{fT5EN^XLdS!tSlY^CH1?Ip$%3m!2$A?^7w zn3xjhgk}kfBNGxknSPQV!TKCP0pf}*iFM5-fHPeL_(c%!C>Q-#AlP{HQ2wRfwV5|t zF4;|s6alPB6gP5Dh<|ZZ9*=5mQhGg{{HF<4#&Ksp+_{^`Yle2 zqBiL`UbNjGgpg`gRoP5XfxmcC;?8Cv38)UrzJ_qk3RMNKA#%0qtDs5$1W+%F18w09 z2;xD=@O5?Us9H*Yo&W=h@=3pPecxq)Gu_?q+9$yO@;gAC6QGVKP{r{kxsV8P5qEsd z?bq^5H^OcCFjJ0JEO%sN!+=)u2NK-MKTf}lKsVFIWK>hGj+qj?6wZdhlH$z6>T<}s z-=9rLmks(gO#+emnwUe(Vy;5^eaLpQ1Q&{6bF+J7-K!{1|B9CS#5 zyy8)qWGSAbCZ_n|gH^t$*CtzaNYaaJV~qSDk*RlqDhr(Ckb1<=Z8wF(|Ky@vmkx9bs9FTkg#$1Bbwps%-=_J2KsjIX`leHr<{+DY=v zf53kaPnZ(F_*i=nXQ>dL^{48YKPLvslZ-7<()4=#n?@1HW^_R>-$;*;>UD1@Xmft0 zQz@bxmD7aMp|lPVgeL_uvXCNZK$Gzmp_Awj+NhwgQoG6fT zvb2VrG9F;hCke9-a|c)&Auq2Zw*&jz)6$$N{l#tU%$S?hPtxj!ED$_Rl>KMMXI=n9 zQYJ=EiN58Hac%iiZpjfqc{Tz;9799|Vgix(*emCfhN)uXAOQK{)}jcy`icA9m|FH_{$JGs{whz& zV_Ia?8=QGJhFO;0FF#9)wC8>ku>vAmTO@@Nyd!aG_QB zDWQ?SUpK)js5SC8eWBDM8o%=QX4?#QXGbRel6v_W+Q-oK%`B6ihH^j0rFsOs*X^6r zJNUd?(ySos1-#$joAa9Jy+`ny%NkIy<0OSA#-SAyB`(TXsn6NEX5N!c6rn<9g z>Jb*+vx%J^XgO@@YE%3@gkJ4=~bi%S%yuFCAHp>Baxxm&v|#;V^9 zksF9ef}Q=SoE3fKiEa2KUss_pHfn*;L6!J;Z|9+q1iFEc^R4(;-`5f^?O-v0K|7BZ zZxHc6IxI>&AVw(u@ow-bztSfOgxQ+_ec}h75`Qcho}pwpe2{lxeTvgyVI--PJD#Sxg#}F%zaT*ZQvl(;wh^I=d>D?~w>QwlAgB~W ztClV|ga}$LE!+Kdh%wv!@lX$;9ApPbk|ExxPw=SyT%<+EdQ#`97(y{SAvhVyvn-js ztD}f8;F7(r{L|om#=f5{CpjquWd5YDaU;(m4F?t~J zBrplwD51IxAV_J!kP-pCdXZF#p zG&*pi8EO{Q79=%kG~qYtZO8jupap;wt=Z1y%Y>kc92BT+Dt=Z%TX&#N+mk&&gV27$ z+F@JQ$Dq)ZB1h)^9 zmnGCpUP)sE;;v{4-Xv1&TF&#RILYI&wg1TxJ0yx~;G+B>e~o%2jw03{>Ru^4Hc4~W zu7g1uzj9#idS%uoN4ft~uWfS@l+6Gm-e+~CBkz$Thw9be?)YmZr)PUB6nJvAt^W1x zf@^=}ez&yrM!f4c=!`L)moD48BBMU{zHD?{`STz?r@rea{`ASZ@`t^5_v3GS0_0v{Ullt*cKT z&cWr2SZl6sG)T9VXyizLyhsI;Y4=L@ZG2NvT3{TyQ&qF*7MclC-(dde0#lPM)a~Xq zTD=5#g0I*~Nz01yCT@Hz{awBXo-YBh((Y4qp=CnxAK@&(pM~6?FVDUa%gs=8mHS@q zodo(Lkifua;owJLclW7~?bmyU`*ZWve+4_^z?0@XpsnGT(9a33b_ruH(dRUcaXpkN z5npc;n0JyG{jrC_>ceOC}vaMjYp^|&(xm>zT8IEyohQ3--Q6)&%Z9`|HQcG zsuKJ4gGM7DQDTC0Z*}_9fol4`e5Z%ru|Nn0c2DyoXonU@=GmF$sTO9*YZ;f0-;fZ! zY{_j?qh-%O+ZGpHypXDwB6CHhQHN}QCS}v#1bQJ~MS2HKPo7f}TXvede+ERF_A2`~ zvo+JC#L)GB9zG(~p!|U65Q0bG@mU~X1}uubZZHic2WIcW$E2xpV=cb5HEt9!fg|fX zC~0Pmi5543-G&|==l5>2T|zp8S|7Y3MmjuO9EFRrK{7U#dMKIH$B z4QvXm64ID_Gd_ZpLBDxU)|YIsU)xY4^KY8?>7!gs01-0JOv4LUzU#?NYn>~*4AvE+ zmcMVNsh%%-8&@9B6Mi-?erhXDaO_TjY-ENv(?2Xv(+@{G-z=~FZq$I-GGRA|wWDfV zjrSDlXR!R`TPMq4Jv3f*QEefiHwAbg!DGeAs)Q}i>jss_KxiBJx(M*hN6BL=#zjo` zVJl*74{F?VH0C}?7-1*k$b%%5fmf<)LW!pAAmjEOpPUy-FA66l(C0_U8zHzP@yxI8 zi~t{iK+9XC=QbvXd;dMstPv6NH1KEX(DiOV*|QKTca^rh%C$x)pmyWbz8Rk@m6XLL zpNBS2ei+2m(Y};5T$Yn>U>~hx90Q)4&9EVWDox9Fa96pS;*~Cu8TT-4H&_2GEUn4J zs!gxG?xEujKBuN8#yPEmi&@Rzqt)v;^_GW=m^_g&it<#zy$@fWNRD(5-O$_&3w8+| z&?ra!x0Fh=A$%8WkCa!c_x3W`{9nHjZ<0Pus#D5AltF!HB{kOC533ES`JY8u-hz_+ zQMEriW|Z@aN)wX+4f03}hQnv!KXm{daqu77P=;N%bG)olEkCyKMYOFPp1V}UDcDB; zA^P5yr~PHXHtzbvH#aW!{p#@^K>(+9{d*%%qIVo91xn-Ckq!|LZ~;JOi5v_yMklw5 zF;C_Vi}a0jvnjJj-Lu{`cKfp5ghBlcODsH#7%S)QImCMI*m#eSemss@U-AD>b8IVq zd?c>qva1zp@A-NiZjRfo_`J^g){`7tD{Nn#{^?G&=+#**BMSWCbDQ1^7C)=W5`o@G z93NdA6RW)e4b4f~Vk{cCWNHj`3`P*^7aNL>`86B;i#TBraE@&}HniSsuTK1{#@E69 zf9K4;^F)T14_V(S=EWZ0R$#NsM-HANe{M_E*zs9nkKqUf0(!%;Xd?v3^bDI#`p1YjXFcxq~O104>gsF&E&Dm1RucY3ECEPM(Gz~hO}v1emtKMtxgMmO^t+UXk7OO zce-LeaWBS&X;hrlY7h%hWW(bu>+Go2{tB(W`TC4mD6Kppdiw6b5q&3vT!LRoW!|a& z+T(&rhDW;Udqqa|uI%=;K3ET1sudDOfhnb++^s51)Dg|njy)T^A3hRoS;(R})aWu2 zd5fp_wwR8rCBZPE`voPHFmgh*G?A-b>mN!&Zr6P~>>XfSjvfM9C>Wonmoi*jbGC?q zR?d6xOuhR1GK!@p&c0gUjZt@O#8}t&&htC91$2K~F~P~T_3>aYVGRdhGPq^1mZZ4}eY3eYN`J5+eCh;DlM{+;#*^)%4lwt{wa@024v% zzUa0sTO7;T6N zbge$PU^WZ7&+HOuy8Ke_uTaykD}6z_s@hH))j~0Vd~pGG{g-<8?1idrT_-c8F7y)H z-r*#wtEUtFzm4q}K=<(MvY7NdG8+utw`!lh1^4OiUO|*XuG^DOh*GLg&TM@CsCwo+ zVs7kiJ%u?rI5jwid6kN~*{Q*q)WmptBsH3(EKcj!hmgmEo#y#{xo zNWja>b2wwSBwk(qzIx?7LiR{zt$qSLpDc9w<@()6sF+O7)w5@+cOHc7_}oU;m<%Qm zdoc(u`qpsEPess0eEvYL!lsZ!fucFL`G z{|a%G23ISHjo7c;JGSDH^F8cOxjEs{8pFic<^Nvt@-{s>OK!qVj+|(#U*OEp@Mwv+uPN%7i+gauRgx=*T)}?bdXbjFV#`Ka&hg(V=v!P zz4UJN`!C?{68`(jx9D%Ei+2C&`9HbqiZa9(0f|4W_b*j1KCGTUivd9)5L~W&wfguo z^0RvBTO{Bj3PLXtnf$!ByPL{R*Y4IXzDc|L{&yRvFVx=oZv_hGMN|emb9>`Aw~;8! zJJRLAPqv82f|$^;H9bb;U2G-J@;_xWrg#f9qT6+KdNC}|Hp8<#{n*kQ+zuFM>uv{} zFH|y{TQ0%*hcw^=(vT^YA?i7r%lc>uf$x-VNN1}_9DEGRw*CM!zt~mKaz~0Somi~2 z5`72hT11E-RMkak2$v&HDP+e%DlV}YplTZ*f3oqxqw43M5a}AR6k_0^?klGeFFI$M ze9+RJQtVZP#jp-;oIAI1{VV|i+zPkNWg4JUOq!{#5lV&kzfP@xetYfaL(*mRc7*&U zBQXW#rIHV4QG^TwaO_;=n2OoOivT)~GuA$uEAvEJso`>qzMDqhso~`KOlsuC_I`^- zgcT5eK}N#ARYE+J98XRSjxqz`#Nlu;0^oZ&7H5*|L!DT_Bm0nJe0ISWAO^`DVix(? z-U0jB9!CnVs~%mE&A0wDI`aUG9)2+tu;VY*0e%2BICNw-H6{GtMg~)(ag*BB!!Pw* z4k&rsdV|-5t2d`8-8=aM+4t!pcb=)$Z}o2QaBUNfZ1#jrUouSJK})B&gq=+(+dxbi zPnP$5Pn<4-=X=}QJ2Rz)ST_(S9mLO&enPch(4okZqvfd0I12Mf4UaNS-&mvUxQ%>d z#6yYs)RkN&v!*_%lAuipHqdh;z}sK$a8*u864uCD$1 zarNdUrurR$miHeyxKi;4OQp=pfLG3(^x-7|&WM%wP1r{BF2*9!v_qqVYws99KUo&jm{K!X>kFF?OJ>~Sn19=5_J=}ES6^aaY z8LcbK!@(K&Sf(t~J|%A8f^xI+I_wT&v9L}R!UBUBUh$h&1?m)j6b}Q^bJk&ySvVRE zDvH<=NYbG#fjnZiR1X_b`fRRZHl^*7gEbVTb3*7iCteUCK)&=qkSj`bf-I6MKpWtZ zNhK2-T*h^!x`pLMw!%SZqJJ_yR>!AQxyy3 zsMDlD>C7xGAx^~joI((m&O-iNa08{MB|0!zR57bs^2)w#~_ijB6;lr=Vw>M2wyy>Krn%W{(IMOjXAd38DIH;#_(E03r*@ZUue=h=R$-2pjKyci~huNMM$X8Yyq;L zufR1dUhF_jmMuOsf>lgVLPTiU&lm~P;9v6HncDl`fjRj7zpUPWxM%Il-wDBu_Yh%i zPj`2BfMaKL9KyOkG@kk;Qp;0g$%)yS^wGi5*(AcIXpbXPyt3iavN<fB?#e+H?szlhh-1)!ry$cvhl)cMtmr(9F#GG1Xu|Sk)Pf^s3V)F8$=W@z|qk_F~ z^&xI<@+u;(FV(A;VRHWa&#U*}rmlB{b$|30VNcW{4F6rd`OW%Y;0-y|M9S@7AAf{q zrI(x|rc>jH+)~4MjVD>l!s$=qiX2izZ}MGT&9LiPO>R3}TtC9?4&6ms*B&-U-SkGo zmeIGXMU%)IvF6K1%Y>xKJq~Kiw;STEXFcL`RPECHnI|907}jtv;r|0*h&y;H!yH|*n<9|w%=EB zke@3CZ{%s`rbOc10J6-D3)L%k!Cw=fL9RZ!wR-=ZzSa9*)xJAVoJGlzHa`dl*9cYz z-291m#HDvd84MaY1BQUg^Bw2Xx8z`7E(mPv%y|mN_{w1rF92sn<14R7eTN=>x_bXK zv62$ujvm=dRj_j^dgd}^UrgX=a)hdmgr1Va2{IU?uVIFxQGdRIRVzxTepWIU9mjJI zRG*|DV=5WIbFAcNPRN3s+SMsI3$gaqKu=g-r*OQUt?caZ#O%RQ!U&RPe=pYA-)wOi zj+dar{+4wa3vX&qsk&egp4MN|I`FK5~z$S(JKpws}WaktjrAhq>W?(8< zK|awT8hl0pDJC~ML}_K&M=h^216Gi+K7+%}!hsBxXgqzd6!S*bCMr9EOE$pM;Qlod zUemLa!*JU*5lYRZg^mGsI+G*kAx!MDf31(07%A(MM5c$3bO1_KlHLSzlyxO_rg`mU z{rLsUxRP*qsubJGcn*3k~egyA(7BS-7))>b6bXx!dkse}mTV_m9PdPCLN38sfT z!@Qg<{ZuVT2G;OK76omNg28xWmFhs8(vBT-t|B3*Bf_K;cecJ7$g`qYx~=_R|K)(> zK54fY1I#09{(_znXm6R3-rBwPusXK>y0kuagwAAJf<3dBWE{E^0?fS;!w}Yf`FKo+ z5w{i{%kF9KtQ24HUuAYH;lM++X!x_Aa$@$oQ*zJ|o(h&y!dFLfVvrkR<_5``((Ozj zxtwqu#0_l4r7nc}ON2~JYFL>O?)MSJG%-Cp1pk`;fRdYqxIAlH2%0~Zbs@-M6oR$_ zT?`7^4Ge71@ctf}@T6==iibOe_DhSs5$}Y?jX(}J%m9CwX0#-^SOyrK43(mu>EPam z&-Zu&gok6*qkpF%mVJUc57uF|YXc-<_d^&%o5h`1mR=W0Yq&9M$u`!X6r}dhWM5hv zf1*o3K24?k!s4QM60%~%tZ>>Lu38cOlTyRdp{&a;UDs(OB`6ZmN{*Qnlh=PBO*Z1v ztodt2bRHfX9GV$=A&glusKRTwA(x=$b`ysh3g~9!b=Vkpl$WFCG{5;pxD}-6^4xr? z^fx|%=GIDq&jQ)md#Ab6N2E5zU>K&9aZU#(pr%&g?LXjkgP8nWU`aztZu(vak1BfO zW+cg5xP+Km3h%OAL$Q4p+KDN*U0n`ed-Zyn z**=fR0`EYif2oh(tLLl;-&a}m7v~n2Vr|ns-9K$p10Aq~aCW<%SUmnU^{-HmHAEQ^ zH+o;cQ~Td{*1!Ll+_;Sa*sig@?(Wg{>W$me!-uP9&uskWcJ@FUc_KD04{kc1L1 zfg;J#3G$Wu5OI{sJ+a+xiR_yOi76(KI5CwR97D2%@P4SsGhD$@EKIK@v@%-RmHz&p zCg=aL<^c}L|GRh3Zax2RUw2P;EC0`SJgxjcUMv64mgfI)W&@UkX)Eb{@a`EQC6AFd zSbRd8V)jIjUdT1n=ExglN!a5^0MW0cEB1D=l!Y`gu_n@mWhYkB6Z@|& zp}%v6T&Tx1HO7~Z8{tFf6-8glb2-i~;&$$Qo^QX|A|ik>!F zN!TeUon5*DyCD>%YOtNyoQk?HawW&$;Ab(NG+PH!* z{ZIe`JQWa+9U{S1x)NZs9VjL^_@_I~R6> zVzd*{)GLul*v)_CK2wxS?P5BjUS2h8gk8fEYY}Y<)1wnJ(+PnI?K%W1i^`-9jSr5> zrA#X^eyyBiII}&-Go{&Vq|M zN#B6Rcqy)a%cZDQGB{m`ndKN0A~(6b)Yq$qLsZh%nL*7NwGZGuS7tsVBypX_egiyW zlu~xz4^qycS^GhsbfXF1Pg%S3e(l?LiFHhjjR=1F(X&`C?iePbgu;+j>#%N=mEW6I zSTg`Njfx{R#v~gOCjhv=)nxLtb-bXbKK+0BgufCs>$7+o$p75i%KyAQPXqH0|LZ89 zm=DbPEI+~apFR6}dUg9x@7|uCJuUmsHaso+kJqyQY@z)}m|AqU(O~lDBM@a-c`@x* zDyaQ(y%r-mN3+GWgZA;YuYaswy|jM%Olp|tuZ39h>EYzaAY9lyoP04oIXjd_B4yj; z@?0w0<}Np!JPQAMZAf}K?5{{!dyS%l!(*xO^dusHiQyz7s_bH}Fqtb16|;Uo1GCe~ zDN$**?3Z{QUI`R~^zh)!pltdwm#|c0mh_!8LjvhX%~;H4yc2>~OBsOK=+aZknW+~M z(An`BucuoyNe7~Tcc%!R9i2eSB(`h%@JKp|W*woJ=5akZF)`{Ln*>`yp@R6=RJMGT3R421`#Cu>j0@%#A51cFD)1pq@!aWr$x&pSY|p5ihAU?!IQ z!r&6vIWbX%KtmEF&&@scc^TE_O+C$KY`NzNsFC>SC5C@vf<=Jswn7)w8Hry^5I&7Z!sz*6&xdigp1~3?z z&n918DwZmf_{alMX)uKn_CB3kD2(JY3+VTYGr)k40bR;%!f!JZxXP3l{BniU78wdS zgbAR;kBuz~>b536Lt(iHvn2v-PEb&$q#p^Pwwq;q2VTtbq| zuxKbXjE5z5GZ;kBgbfYB1i;WkCMy_^lK=<|m*Ag>hqeT8<;Yr&<_QaT;Ln0Ec!hCB z$VBN^WGvR({~(4uh18qkqGJH8d!&#+9QhqgU1&p|URhie>&OKh2uLS2K>_G%&89%Vb%nOicm3hcqlcA^c?JAY#|) zkua!vzky9CbX(g>VZz zsd2pP21sQ)D=ht5&7O z#u+shwo{p#MQlhb>S#vYgLyMHBZs3IwWy2P!R{=$oUmoM z*c+4xf(1LI3Qy>FK`^0|0~JCr;AqfI2M!d7VouOxtxJGqqkt1EDdb$eMD&nWuB@m# zS1l>z!fU0FXK(j)2phEm(P<4gb4px-n_tynr z<~WQ%lBA>X>6GljD+*BmDR7gTN*+p0&m^bP!&9lF$*CAVWW@7;f2YR>$CB|`MC{W{ z!G@-l7m0-OQ+I!F_Y%~bK1N^EZ#tJ;0AtLTo+q{9u6x*-Q1SGemaGB;v4a1KVkclu zzkUUbF4mzF8-q67H`MXU*UIHgu$tJO&CkK!t0jxIjL;*wZP^U63|v|#LY?-tyj*IU zVGUUHB*Qqj5I*@&Bz*8yEypb9(#E}bBYLuOBJCB56>q+{T##c^I`bxPO*&ytRv{h7 z8HJ(uprAFLOIWAfx{?W7>7nj?Y_c1*2FxdFB3>2@9y1yqT%M#&ZX}>A|TeDK!B!_#AuE3@rGSB>bB4Gv#7|0X64CN#&-@ zi|(5MY#P67U(DnY3yuja>|(cuhh7+*VlLB*{vuq6kewU;^*_4*HSXzyj*K)JlofRr z+-(i==1aaWjjVpOE|p)K@fVjMAq|Hj#sSm;%aN7}9Rs8n1I9s?B=LD^Yf5kBz{Z%?fjcQ5(ZMbrk+U5mx_y{Na`u{#FQHm?Gz-x zD{lSTE)9?O;XrT|=p-Aq6~P3hxVwnsmFdNLfsh?;G*c9dvXPk1G*SdI`SIn&W76I# zSIZK{L>H#Qs``z>WeJ*?LGY?0X@)2;5eZC7-$LO6fp~ZNQNbe-yCwTNZmLor?R8Q-6$~4v<&;`Zz z7}N3MUNN*wNT`4oq;+E_N4ikVk7&UN+S8WHoak6XtE9CCHn9c{gsuUDEa-fBm^^4s z;(nzHB?Ak7)%aXNEy|)Q9U9)^HQQ_Ax_oLESdJvx^5NTdUgjs0&W|Hs*BCHjKZSqJ}*x`JKUqKi51$xss>&qvPi zhkumqJCC%`8eRT%Ze9ZJDc3T`r;^$V&l2-oJ}+75VwT0GYmJQ7fj}YP9q9y~7VcM5 zeyMlQUge@Ct4$S7^qC5rA&QgSIf-_t5+@qO%9N%_IXJvPHg(mL)EEX%B}q|)ssNEE zPB(#C#=zuv>l&j9ilLuuZ3p0~U+vK8(A+m65Hn=^^fHoBSXL%%ibO>6Y=i*Cd$eY@ zf&wvEmkET8IYR-&1T1MT3@idppbohaOXPrzLAI0$s2`l6*i;o-sLxId6~5fg&(CD% z)H!B`SEomJk0rjMih-2-vf)nGYuxh6?H%!_F~hUY1s~9Z7436<>YzX*BC}@#k_skMLihHR?U0?W97GJ|MKIzV?B*>$s z@(F*%FU;xnNpG{SkX!kLFnac?M1%P|d1aMLm>1|Y+TYPi#!gML=*p6)4C4`?1{OeO z!LN=&K<$xIaS`po^D);QG-k070_idS*8vr}A(Lt}cfwAI9G;rhmOm;D0@P z+rzh4eyS?7wGwrR{;9c-+N=eXzBvBF+l(LyHn~l4%qzFa@Wlj4dLT-#$;Qb-uehNd z(uC&_7|L;fLC)<246p#A8_kCvWy^PIX16uGb~$mXz$B?%25< z91S%T#>em-zG$2*6w%!6EGCT|UuuOI#&LFEu%tV+v%NI>K@@d*fvyLbS`f@2Ar1Ss zd$=P!l`QR;A){e3AX~GJaAg7+=9QP>bD-Quu+4_qh`PZ9kiH;KX;zFY5ISL57lBPh zozq<09E-r>Kwx*Xi^&V|l)Fi3)rAVS_C^H+bMP*GTv75v6>-O`1e*$MDbsbT4j^|I zubR`{1MQrbu_c8^Wg@a+X;q;5b#S3&a^KTN`3#Us?@~*&bF4M`Gknca&Tnf|2C22H zN}}viYdx~4667F~M9NwT8&if{>an8HA()06M-8%ZY@f94`n`?lV~vgw_mpc)wRtj1z4BXQ{Mgq7G9755I`_tLI9sos~8aKlcG-w zynFWcWDGEDQ8i&>4yDZn*_S?Gr*sE72=H$*ob^pZ%JiY(^z>M7x(De@G-iUUs}N9y zc{>O8)VgmOcaV3AJIGBjZyESW&dqTM+zg_&0T;}(dN@OV-8`#WN#witdgMh`hhD2k zpZD&jiw5F#Qmm0L)Dp}i-dO2d+}eMsPfL=aZE-lfPd)n^i3=2IW5ck+Rw9ivRPUa) z^`<{3k6I{Jik@G{D%`0N^Ch&z>k&GQ)Yh{$cEZ`kqC&N)AD_Oe496`iJZetc$(Nwi zX|vC?G+vBxkC2mQyRb{?>{^E`@(_mBhma#TPC(N&4VEb>bVOq)3+z;k2Iz>9tN87F zt;c0v5clNR96R-(98K+)bW_D7E4drI7jcmiP|HogjPCzdPtrR&5iRiK*l(86ruW+^! zS(_ubiE!Q)}L$rYs)&r;^JA>JCADDTJu}D=GhEz`)@>JsfcN4wE)XdgVs8o zWNE$AH<8vnjkx4k_cCm?4*?5DTw7JmN<4z27VF7aFi}?uPD3+$0_l-;I3#BE^W?he zQFgf=CbwnOP`oT@briz0`d8|GvmnIb3a2drGI)I5i{S(NRpS&WSmZ|k>}RTQ`NVu0 zmo2w@UFOS|x_{M<%KzjiI#vL6xCpRXseJBNo#-!@7b3d^p_DJ-ehb7|JXR(5vp_sp zl>m)Lo)CH!4`vN~be{dNU3QI&vWdhvnz57qNU^k-VdQ({Xr`3QAPk>`ris~?1MI{v zW8AlxDi~eQ_fYR)=#o0^xCXxHDvi69A@pzkX+r)7YtB#k3CaJtr?0n9&;PJz&z{|_ z{14mkwDLcAt^5yLng2mf{~00srwGvyoa)n^i&KuZ!82|u8*^B`O*!3WV2}~d$g!!# z=xA=f%Vs8bgs-yw5?_G}xz%jFDou(B(O148N{4U#7<5xl$<`V5>W^xUjdB}9!BO;E z0?P4PkR!URwgx$TMoMR(Ypycbr|Eeq&s9_(xPGllBp)`ZQn>q>GDlnL^3`uN^i(`x zlmTzv+d%=hbsh;a^0w^AN~MMEHfu|)mKp)J#j>XP00fyRG=GNx<=re9RlP+u)hwGq zA+yZHn6%GQkfOiQbc@g26H))8sZ%?$$HLS78BehO*Rv1l8Akrcy}hmcf7|i2^uLz= zx0U*zq{B%(M9KZe6BwVXcxu`mucAh2>nS z)B=*qGI{J%A#QWX75!H{X>9#kiH?CBt}1SHNU(Dzno7~i*tI8*KRW~E|7^;V;LU%5C~5{kk!Bgfgx3*6ayy@ zv|^L#;nb8C;Nv;;y^}_qbcXDJGK(jcGN1<9L5WZr++9dl`P-iF>0;PLgF6T$ls!hx z@eZ&72+Cx$!yHFPrjHMu?`8PEv{EHVT-Cy)JCiw<_vINFno17N zBt3*`Bx|NdFw0H;A~iiT?Y%^q*RNu2O26Dso5(glll;XD+9^_FgHwp}Ctt*Xv7cPM zz&xzm)L)$?KF3|Dry9+RUT3|m$j_b{ue+XXU&0Ny_bZWyF*)K{Mk~{wi%GK?IjT+@ zsV~tRp+)0$;31$I8dc(rX!X%wx-!J6A}$}G)3vdyNq)aBFCUCi=v& zV#w+s<6bKHVAzbcrN*a|Q!}6q7g^t8QqOsJ8C}6&lvLtM4=r8tjt-8_CZ}Tw2_XAB zo)8cd63B!MGhqYT-=>V@Sgw%8N@=W3wA03W&K)kDrA1k1N!Hc2xHMOt>}^0bIxM{& zeeJ9$aDZyU;d#4w6H-aWRhw;AkT{cET&k>~)$ucabR1)UBoc(g+DY^?9r?;Rv9vJl zasx5whAodqDR($InnXN&WNKoJO`!L4Bw;7T)FYOUCc#ZF=$lC>uF*-0SeDlO%2o|< z8Vzu0hL5fJBUOjU7%q8R@L#Q%lCjCiP{X7G@iXXd@2nJgAzP4YQmx-)u}73@fN(g9 z*e_lW*zsQ05ib~v@qx3MEoZX~29j72ilWRV-?`CL9z|mN;P|lMweWJ1zLfUd1DHi( zMj8E{q*2<>Lb~I(20uX!91W9=Sb`Ufjz_?OB9nG?>rxmDD6wB~y2sliBQTe*NlOQ{BrTlOuzTs?K1tq_ zjztqQmveJCl7qAb%Sc$cRdj8kE+MKhr0W>9iBL9IRSTCAEFT!v)}T0m@BeMlEW;FN z>UO$?xN%z~{5PO>Z-<^B<78g^9gY5_3P=}5J<=w_EWXR>s^w`aK#8h1SKMAhK(T>+7&joJ0^% za(<%&#r3d!WOpFppBp_I<-&#h)OB2Hg%D~*Q*LEQ36UiY543l5=b6gfalhm$GUYGj zSB#I;S=xF%?Vq#$e6HYolM|zdQ#0w|gR|2(B%#*^v^HPz{j6U;Q7JBUP8Ja@t{l!) zdfKeDNQ{#7S;7~HOADt2;q~upGy}SCu zrN2J@Xzk12>5vC!XI@ATy^tI_oSsRIC4v5XyG5a?3EX$mV4%Fi>)^gpSn088udlm% z2Q%kRVp5u3#xYqmgYJnOi?4fREVHzqzK(m!Ofm8%rdfreyq(1*zXamR{{1oSONRc% ztWZinyq#t82a_OK>CR*R0^+2&>tOkzVgmaNk`b-hDLSuKMk6tN@{wf6C_0r@36UEt zMHcg11zR*(Lu`3t=rM?^2j8DF3xLNR`uvlhKmkKS)H|6hR7#3hr94Q0&wPr89&RzUF89mG?+<%;){Wf^TIxWs zRyn}>6I52C&`5P?{|C6Yvl3=V>g|B1jKdy2$JDfR~ zpgcvm67iwq`Cb~~LDr4Po0peSm-p_D?X>l(-QE&KD8xf2e_2aWyrEL`FWEv`24=#+ z$n(Xpg;w10Viwfon6Az?nuyY&;5GxCs^>Nev2Dm$NIWKissm6NmI{KhKn+%bG~w)$ zSTtWME)qvwI%SC9N}vRzT}u=AK{tR4TI5WjUuPq4ii^Da>JtmypJ`TLAJ3F&NQ6JPvbG44R5#t0!B&q`ere9`|-Ssv=C}3c2!e zKO6VTm24sb{@t;i3a>8~MsOKUs-!7eVJV-P^N$zvS-(_HBp?x`f}eJ+M^>%_{hNV5 z5()TST&@K5W(XtP=#9~=b>lLs#{xOI236h>S`5whv}x#7*cuejxI@InR07MWhHclP zn$y-3HO?gx2O&OYTsQqOc1!a0iDo)0NcuyH*#UiWaRe8(R>MyvVg5ZAR#=7?4@Zen zn)jtmgDrTwIOk|!c~I-cG|4FgSFXf`4a3~+kjV)yP+is& zI!n_-6O+kw@)xO@n7}$M(lJ1sCr8oR8uK6CJY%Q?0x zT~eL-00m|k17xfQYQ_EPpyUumsq!E%M!OzrqJgW(LQ?sh6qwvNo{pSs6qAJ_WlOXe;YafulEK}*!lnN zz2^O&eY^Ly&i}XJX`TPK&i}XL{9nEWWS+=lPOBWKlSCT>QBsENCpwAzW`4PVd3z>5 zz^>dlB&hzN(|Tg!0c%eT10j+55pePc2(ZU8AgU5|wB0IcM}7VS?=v(h0&NVbeWbD2 z=3s|5`}$Ua4{jD7U;-Tj5QNmvXIA_y+2rAtaeMz3HtRIsBIU+3no?8e=9{hW0W37! z=q8%QQ^FJ%dw+rX#In?B3ghqw~}| zh~Q<+z>?FPp60LWr?i~a=Hc1aWoyi z1Ir)()!P6gsEZAVR>+IqrtyWR%nsCU!1Gktg4vCy!XC_S)b})rO>DXw&0!at?M4&W zMw~6TDD0zeQ|tr()mJwo*&G`Yjgl9Ao8>Fd2s>fVf2j7vg8#}OITt)Yi4@Ju9C=08 z@Rl-}XN1d{-Kme;aWpoO*Lcqi*JIB!!u>YeohEX@IAao#xnX*6d^kOooH;r;Dl^8C zUm+~7*&OmenC}y2X-}OYwzy%m?zFgJPn=IvJeez1mNR+p4dvVGr__j3L0+-)dV&x$QhUVB*zn>!K<0y) z?DvDP8?$+fwh!=b;0qgyi-UH)OpXLR>~Udwc&pwk{HxrW;y($G%lnib4=ZVqo8oj^ zzprh>F1Ak3T5J5E&ADdv%Ye4l%|-8h1oG9w8vXspHHvutzl>gWEEWqn2rbdv`~Tg0 z`gU9H|L^W@o&Rsc(~AG&wc`J5Vf-I)@n2yf_As5ZsK^*8kw{2CVpN*hUcLWS?Y(ce+dAAPs}rGudo<5q8;hdHEReh)bhl z*twSX3Yse)gWCk;hLqIo)w|WVZ?9edZSC@xW*dG1vNNVj{+ypX33p#~JR=C}m%p!G zd2jvhEdm4~ruN=B)b-kxE9+10t^N6nzdpV=pRL~eX7$k@*FV3#`ta=flMm4#r8YU# zkzpNwNG?s!XO(tom2z?JgkKpiR&w(>AF|$O&1*wPrj5f;k1g#y}!@vo9wDkT1UbpTKE7vC+LR6JI4S!SrTD;l9vy0lnyy}(TVvL?$eRON}{yVkb zUt0a)#$O+Q#EWIK9Uj4R_51%>fAUG~@~!Imdu!i5T6^>y;M8@9{L+t+WGI+9)8JcgEr3oUM@(#x`<{hCU+(Gy~*UAVOR_#@~(>c->WSHJ#X^~vY8H}CTKWX&A# z+EDE_eNLr#!idj_az_b(yZWe7o2$H!yu)G)p!kX2ptUCZv&-@mT56;C7jUvvGxk+Rpl+w}kL?rG_N z+wipXKd+_#ZKeL_aQx2ajsZ{6V!GAk^{vU|u6z*|J(JkYB~iF8yE#wNV9`*$T|^%h zK_poI21NlCmPypa^ytLQv|?WkmdYV`PDAa^Bgcuyn(Z#4 zeM&kqQ(Jd&X`bFA3|6G9`WF?yAwaKCsaj#?x8#Ys|5tFVoyG#dhur__?&;fW#DCwn zyA}U^JD%45-`fATC;odI;UP-MSF04@3i$f(6zphv5LyA z%)(evD|HtZwx7V)SUVV7EcHvL8 zD_6M1h}-H6D;@96F3~a88h?S?T^7~@?W@Id9OGZ(0!#MVWyA)kK3^8qB(w07Yt`$Y zkWp5@{-FA6Z22V?h6|^`j`htawbNe`I}aM@#_xYz|NOQw&^($4)M}-)vdd$9$`~gY zOGH7VH!gViAC~CJm~d)T5ee+I`f0S-{LI|(82-g__nO!5@!>wCD1cKCVds$mLHB_K z2_(tXqY(?#6Q)05zSYNQG2v3f3qOrO(+!=8k+TT+Q)>1f$(kn3GK5@E#!Icg8nF@M zl}=;Qfs?f2sN0c3N>bu<>~sSi#7>ZkugOl&kz{M9l(W8W4zy`LvMB~uAOFF;xdGnO zMDDRwj7(uXO5#Bvgr~)An!rLLu`I)_>*QuR2?Mm$I3MA#z+@g{JvzWwVY|0;rx)W5 z9FSI_IPVhk0FRi_h#gwY6qe^RIP9k~Ja~?w**_s(ooWJuk~x%2eZZhVG{f=RfJfm! zM$nwYTN(sW<4ONn21i@%lQvr@!qaDI9a>>2cA zRXXFNP$1mU;!>`R8V=jC`qFS!6;>}&XN!k$@5@7 zGj{@B#Y9eVD&GRS=Gl)Jm0k~y4h|hokEW()(jei+Me#&p9I0z3{gGk`aZR;TG9=oq zK>1kYpeJ1uS<9X9*%KiZCRQC}eFoj8>a#Ih)DR1u4P958SM$T2I(hm;JB))bIESJOAtMHT*w&c6aY-+5fiTY1#k0mi=$r^1tg=J1kA9 zMmrK1xQ@fT#*A+L&C{Oyn5-8xMB$9f{{|147{Fy5|4+JOCV z-@ZMr{U86W`~P(J_5Hs+%^s}n`3K+s>z)6g@|~|8PY;cER?1o%!SY}4p51+V{_nn? zo;`gn`EMJZ{XctkG4G+R0^)ccXzS_hZbQ4}ToEL&18vKd`Hr8q{cK>z{^y1#h9LT^ zH#3cX%}k3by`4R69+DjvmNN_ffi_G7ZKw>|kShMGl>w|s?uTt=z{s+@zYD)7MHX2Y zNG6u;&lhRZ<@sFRhmr@{IyZd>+reD1IaoWED_RD-yVo{W znn~L`o+*3SaExSn5Eqx1mXKVZ&n@OEAYMP;6M~8A5ARmbfE|DB!h`CMZ-Qn2#~)Yk zzrFF{Ra`j;J5#;TpyCt(}p{UmkAG;vs`rLQ;06 zYksNYwG$R}^!0Vuht)_xf4bL(__B@j63`Ey}N#xD5?y=xC2RBwK>{+C+TqduMjWQ&1ezinM3?@ETK-yQOXfD2T7;F0ee+YNl_|5I=`LhHi zgiXQKg}dLcJ^FI((brqQLZe~or#4K@99Jm-*!3~rFCa9Pig~0ThTwn<8t(GkyhM=k z6o+Wvi5NG5xE**4D#M8w0T@c`n4_%yaA)oM{p!_=aE6HI@oAF$?tNRpBlcLgr4gJ3 z0Tu@#2bomj9$iE9Q)VF(%Ag5_$h?Cr6mtLTsngG%9vc#9S__1Sa^|ETZDW=PT04KW z`u3+!b69(n_=&cNF1zs@xjKKOHXq!){RUa zOrb~z6sNo_%8DbSg9~(cfPA^F-+i|!;?dFR;bw|Q9Vh)%Vy-Y(fDY?Og|V>4x&EoSN=aK9@ut!6y3^5hzKIDEWmYJpZIV{VvN6n-c`WqB)>wYsa(OEA2f*o%oP_3 z*xiGAl}6YHfBQl0@`bevzvq&;N-S z=C_85>zbcETQ@&;y^79HzF0hg#L8DPrED1^eJ%qY65%*ryYWu->ThemeG5s})ti5L z`Y7z#ZQXsDK)oqVg0j3VR3qvdsok)FMP%uirXKN_3~=jZ#~A&T;@KPFzx@qJQ}qqVx|u)AQk4|9w_%A&Noy;iqqoP34r^}V zu~0VNDzuid2r_OPWK zTtLZ!C{h4|JLY>nT*wLI5vbnUXFzA`zxiSH{=>EZez--)I$>k$`!_4!JD9`qtZ^)F zN;F|ZT6^@{>Yb0)uHCMk|9*?vUf7^^cUxJV*o}`#!>eOhTe%yDj;Y6Lor0AmB&cJ> zmM4P$H=eG=OaWo3v|axHJ?O8N|Nk~T_0Ip$SmuNe_mIk+l~)51 zBA&4OpL@_;YxBQ*_nzLC|Nk~TWxo=e8Js$loFM_P2tCMC-o9f8aP!!q(TRiUspQb? z)O6CDJ)C?oJvKN#cqloQo}L==+B&;-5k`*xI^O24Mlbb4t$%raa%utsY^J9tW~YWA z#=z7mvR~l)Yh=H`fIZIW-A`0-$D09*{Ie?pD1-_?^KkNL3c;BkK8%15jSU`7rc>k7 zGlQd}>B*_&Na`1-y7c#>se`y#(HWSeQyt~(2?~^yKRBBj9VUqxS=Fh@!I>AlnW@=i zyHrs9`Y*4GKAj@SLEAEgJTr~MK1_RUtXF6br;_6;LX^uv|MI#-N~idBAfSbpD|^y& zB_HC7NL6A-#%LoBGzK*7uEKhL%25J!2Vw^KuljS#6}UYaQ#-yxfYRyQ_$G*1{=|kQ z-k~-TpMi)i^kQo|n*;f{Sjzcjt$lA20Uex4N&p?S$O?p)lvhAVY1%=l)Zx*IL+K+k zd(z}eGA=iL$^zzKYJ70&MGVF=a>!eP{&%dDu?(7*KT_F~NDLKA)()TrF*P`qN-C`r z5*fzJW%^V*LdT9BIE`06PkGz40kR2A_NQSO*B(wz&!7&^q$b8AuShgP7#+?VDq0~l zEX}(HduD2Ie0miB;*c|JU2s*Vl^wz8z0U{D;23 z=lBn;un%>HeW)|#mot*mxM#8{__y7X;^$__=PO0HP0 zAlYJ34jTdCpK=RIxY;(Dli_F*ERyRC+QDashF~gaAooO~Qi`=vYa9|_q>>|ph{X@9 z1&LW71RWeY9eaAy9GrUYPik;rk+1O9Hk>>-dnncpkt3DFqc}6_TyeP~eZdi-vOFjh z`hZLMg=`*&XCe;sxdILuk2c=9IF94z?LvQ_eCby_;+BSHFB2n$p%@K~asjNR_O_NuTg!!GP)2WwzJ^wU8pvc9a|Jq_HvGfV zYy2KHT*6Z!`i;n860z2a;Uv&xTfi_7KVpr*-)cq!JAR8hix-7Hg(&X<>)MJTX5Buf0 zQf`T8(K5!cn}9b9t9A^tTO2BLrdZ7LwM79UCHMtDzeI2qGrp1c7>Vj!q!4spMB!?h&%( zDqAWqQ(75#+fLXUNsJ~lL?mq)0+h;A1bdlB z6yX(*i4J<^K(AXF8?OCgVp1!PzKN~Hd%lN;$HWj1c+-PZ>50SM z&t!2aAtWLRHgpy$J;y4!B33e)CbP7J1eloI=>@-%ER~9o5e6c;B5>_e3oLLg{GUh6 zbkmrQBSso*r6MfQ$|O>#w1Tx^@lP-~vfH$gpuWln_|fJJ>8r?xwoP{_$NY=p?Cgv4 z_cj?1CcQ{BW2Xj=n3!|OaTOa11JgfpW(L_x#k#VDRi={-ji@*kvmj6!OfYoL%$>i@ zpb^SyNaNNtc8>WXUL$|cnn*F!)zyyh&GmC?sNtDlC# zM;LX~wUpiafl1v|mNdv)+PrlN78|*fgdOuO39e6pTQ+fObi=*rm)Z5c* z3vOK>tqz$sgJei=SD)OeU3&n%UHj;*$!0q?8r`<~|4Tw*mk2HCK_;_C-n-%;mX%`G+VMjAYX;r!-N^^9wdxsptD!)id93VgL78*{j8v8b%gxf42U^4;%me zP4(^D)rY^|@*Vf5*89wgp*7N`3uT*nc}4H2{Ss&dlVqe_n7HEec!AtSU*unXx(xPr}TiQLl@DSlv9md~QcCE+CICNuYBqAWC5gp(PTi9j$MKY&_GVCImXWk@h z?nN?V1a<{SwK&!Kkien{YNqOMDzmyOZz$uSK;XcL0qPjYmB9hccVwtEdgMG@;sEFJ#j-!dYFQvy zk3>mh9Agoikvv+I+5t`Qc?@qNn=7Mn6#O~J@TF9>AAM#0yblp6DFBhqRGAam<0CkK zsd9ZJQWjHsRF3o+ntt4p#uTrzu2E8?tzAkH+E1^7U?&xU22hbkiG~!TkPTYV{ZLgW z73QTPXL*)fQ|*N{-&9u9m335_9yD%=PQ(mK$yuK>2q9oB6a)<<7d9As={iTFfDL%lRoE&9s69QP!m5==9F=$dd+K{Spo$n(H$2)6g>4c{_!Ji1CTI zhs5d7c1fq}jF}<%yTV;U0^LEx3|EM|#^CT+YCH|+61vx}35a-e*x}$^#s(&guuSJ1 z7g3odg9!>e0l-s0?vc?vz)4_00)yPaO^{<7#nFo<25Th8qO!7xu2R(~abM zp%DRdzA&hRx?Tj3hkZCwQ_6TmLmYD0Kv-Xtp=jO`q!T{8Vf~^|M3=NGq%gGbOy^nL zl>8qtA8?5L-@CW3*F68<-QAM^x8Z5s|7zX;YV!RrSIS?m96A#D&Ow-9RU_(WF9mN# z1(%kOp*c&C?Lk(L2!DS=dxu|QDP9)b`&E~5%|V*H-X=kab_Fj*cy@XJ-?388pZAFU z4r#yq{ZgisS$rv1_!Zyj(0g(Eni^mG>`hByx*2-ppoyM<+6%{#!162EKOU9axc1@t z-MhpBN^GCJ8r=p-2?4K57H69(7O*Q5B6+iGqedl{&}3&SJ|z{Ivb$)jiq z4UZ;eHH3V6VQ>;H%^`A^ThdYKSESa>45r4>YBm&QnU)sQ+MNUZAo;*d&-_dY2(72} zw4T<}dRkBGX+5o{^|YSW(|TG@>uEi$r}ea+*3)`gPwQztt*7<0p4QWPT2Jd~J+0@N Oe*S+krnE``Kn4JgQ(Y7Q literal 0 HcmV?d00001 diff --git a/kiran-authentication-devices.spec b/kiran-authentication-devices.spec index 40f751a..627aa85 100644 --- a/kiran-authentication-devices.spec +++ b/kiran-authentication-devices.spec @@ -1,18 +1,18 @@ Name: kiran-authentication-devices -Version: 2.5.1 -Release: 6 +Version: 2.5.2 +Release: 3 Summary: Kiran Authentication Devices License: MulanPSL-2.0 Source0: %{name}-%{version}.tar.gz -Patch0001: 0001-fix-mf-iristar-driver-Fix-compilation-issues-with-st.patch -Patch0002: 0001-fix-ukey-Fix-the-issue-where-only-one-ukey-can-be-bo.patch -Patch0003: 0001-fix-feature-db-Fix-the-issue-of-not-obtaining-featur.patch -Patch0004: 0001-fix-ukey-Fix-the-issue-of-duplicate-binding-of-the-s.patch -Patch0005: 0001-fix-kiran-authentication-devices-fix-QUuid-WithoutBr.patch - +Patch0001: 0001-fix-kiran-authentication-devices-fix-QUuid-WithoutBr.patch +Patch0002: 0002-fix-ukey-manager-Fixed-a-crash-caused-by-repeatedly-.patch +Patch0003: 0003-refactor-Modify-the-code-according-to-the-https-gite.patch +Patch0004: 0004-fix-mf-iristar-driver-when-the-iris-face-integrated-.patch +Patch0005: 0005-fix-When-the-driver-is-enabled-the-device-is-scanned.patch +Patch0006: 0006-fix-build-fix-file-install-path-error.patch BuildRequires: cmake BuildRequires: gcc-c++ @@ -59,8 +59,9 @@ systemctl enable kiran-authentication-devices.service %{_datadir}/dbus-1/system-services/com.kylinsec.Kiran.AuthDevice.service %{_sysconfdir}/dbus-1/system.d/com.kylinsec.Kiran.AuthDevice.Device.conf %{_sysconfdir}/dbus-1/system.d/com.kylinsec.Kiran.AuthDevice.conf -%{_sysconfdir}/kiran-authentication-devices/drivers.conf -%{_sysconfdir}/kiran-authentication-devices/third-party-devices.conf +%{_sysconfdir}/kiran-authentication-devices/driver.conf +%{_sysconfdir}/kiran-authentication-devices/device.conf +%{_sysconfdir}/kiran-authentication-devices/ukey-manager.conf %{_usr}/lib/systemd/system/kiran-authentication-devices.service %files devel @@ -70,9 +71,22 @@ systemctl enable kiran-authentication-devices.service rm -rf ${buildroot} %changelog -* Thu Sep 7 2023 yinhongchang - 2.5.1-6 +* Thu Jan 18 2024 luoqing - 2.5.2-3 +- KYOS-B: Fixed a crash caused by repeatedly calling disConnectDev in resetUkey and destructor to release the same device handle +- KYOS-R: Modify the code according to the https://gitee.com/openeuler/kiran-authentication-devices/pulls/17 review opinions +- KYOS-B: when the iris face integrated device is removed, the memory cannot be released and the process is blocked.When the face iris device object is removed, restart the service to release resources (#25243) +- KYOS-B: When the driver is enabled, the device is scanned and the corresponding device object is created.When the driver is disabled,the corresponding device object is released. (#25387) +- KYOS-B: fix file install path error + +* Thu Sep 7 2023 yinhongchang - 2.5.2-2 - KYOS-F: fix QUuid::WithoutBraces for qt low version +* Wed Jun 28 2023 luoqing - 2.5.2-1 +- KYOS-F: Fixed an issue where fish's UKey device could not Verify on arm machines +- KYOS-F: Remove the context class; +- KYOS-F: ConfigHelper is responsible for obtaining device configuration information based on the id, and DeviceCreator is responsible for creating the device; +- KYOS-F: After the vid and pid are obtained, the configuration file is read. The driver name in the configuration file can generate the specific driver object and device object + * Fri Jun 16 2023 luoqing - 2.5.1-5 - KYOS-F: Fix the issue of duplicate binding of the same UKey device (#I78P3F)