diff --git a/.github/workflows/cmake-linux-fedora.yml b/.github/workflows/cmake-linux-fedora.yml index c056305c..db134803 100644 --- a/.github/workflows/cmake-linux-fedora.yml +++ b/.github/workflows/cmake-linux-fedora.yml @@ -20,7 +20,7 @@ jobs: - name: Install Deps run: dnf install -y --setopt=install_weak_deps=False git gcc-c++ cmake rpm-build openssl-devel pcsc-lite-devel qt6-qtsvg-devel qt6-qttools-devel gtest-devel - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: recursive persist-credentials: false diff --git a/src/controller/CMakeLists.txt b/src/controller/CMakeLists.txt index 582c8798..6a30eae8 100644 --- a/src/controller/CMakeLists.txt +++ b/src/controller/CMakeLists.txt @@ -22,7 +22,6 @@ add_library(controller STATIC logging.cpp logging.hpp qeid.hpp - retriableerror.cpp retriableerror.hpp threads/cardeventmonitorthread.hpp threads/commandhandlerconfirmthread.hpp diff --git a/src/controller/application.cpp b/src/controller/application.cpp index cb6cfbe6..52f8d8ce 100644 --- a/src/controller/application.cpp +++ b/src/controller/application.cpp @@ -167,7 +167,7 @@ CommandWithArgumentsPtr Application::parseArgs() if (command == CMDLINE_GET_SIGNING_CERTIFICATE || command == CMDLINE_AUTHENTICATE || command == CMDLINE_SIGN) { // TODO: add command-specific argument validation - return std::make_unique(commandNameToCommandType(command), + return std::make_unique(CommandType(command), parseArgumentJson(arguments)); } throw ArgumentError("The command has to be one of " + COMMANDS.toStdString()); @@ -193,7 +193,6 @@ CommandWithArgumentsPtr Application::parseArgs() void Application::registerMetatypes() { - qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType>(); qRegisterMetaType(); diff --git a/src/controller/commands.cpp b/src/controller/commands.cpp index d484bbbc..dc75bf8a 100644 --- a/src/controller/commands.cpp +++ b/src/controller/commands.cpp @@ -22,7 +22,7 @@ #include "commands.hpp" -#include "magic_enum/magic_enum.hpp" +#include #include #include @@ -33,7 +33,7 @@ const QString CMDLINE_SIGN = QStringLiteral("sign"); // A special command for stdin mode for quitting the application after sending the version. const QString STDINMODE_QUIT = QStringLiteral("quit"); -CommandType commandNameToCommandType(const QString& cmdName) +CommandType::CommandType(const QString& cmdName) { static const std::map SUPPORTED_COMMANDS { {CMDLINE_GET_SIGNING_CERTIFICATE, CommandType::GET_SIGNING_CERTIFICATE}, @@ -43,7 +43,7 @@ CommandType commandNameToCommandType(const QString& cmdName) }; try { - return SUPPORTED_COMMANDS.at(cmdName); + value = SUPPORTED_COMMANDS.at(cmdName); } catch (const std::out_of_range&) { throw std::invalid_argument("Command '" + cmdName.toStdString() + "' is not supported"); } @@ -51,5 +51,5 @@ CommandType commandNameToCommandType(const QString& cmdName) CommandType::operator std::string() const { - return std::string(magic_enum::enum_name(value)); + return QMetaEnum::fromType().valueToKey(value); } diff --git a/src/controller/commands.hpp b/src/controller/commands.hpp index b46ad429..f13a1c73 100644 --- a/src/controller/commands.hpp +++ b/src/controller/commands.hpp @@ -29,35 +29,34 @@ class CommandType { + Q_GADGET public: - enum CommandTypeEnum { + enum CommandTypeEnum : quint8 { + NONE, INSERT_CARD, GET_SIGNING_CERTIFICATE, AUTHENTICATE, SIGN, QUIT, ABOUT, - NONE = -1 }; + Q_ENUM(CommandTypeEnum) - CommandType() = default; - constexpr CommandType(const CommandTypeEnum _value) : value(_value) {} + constexpr CommandType(CommandTypeEnum _value = NONE) noexcept : value(_value) { } + explicit CommandType(const QString& cmdName); - constexpr bool operator==(CommandTypeEnum other) const { return value == other; } - constexpr bool operator!=(CommandTypeEnum other) const { return value != other; } - constexpr operator CommandTypeEnum() const { return value; } + constexpr bool operator==(CommandTypeEnum other) const noexcept { return value == other; } + constexpr operator CommandTypeEnum() const noexcept { return value; } operator std::string() const; private: - CommandTypeEnum value = NONE; + CommandTypeEnum value; }; extern const QString CMDLINE_GET_SIGNING_CERTIFICATE; extern const QString CMDLINE_AUTHENTICATE; extern const QString CMDLINE_SIGN; -CommandType commandNameToCommandType(const QString& cmdName); - using CommandWithArguments = std::pair; using CommandWithArgumentsPtr = std::unique_ptr; diff --git a/src/controller/controller.cpp b/src/controller/controller.cpp index 61ce39df..1b1ac831 100644 --- a/src/controller/controller.cpp +++ b/src/controller/controller.cpp @@ -228,7 +228,7 @@ void Controller::onCommandHandlerConfirmCompleted(const QVariantMap& res) _result = res; writeResponseToStdOut(isInStdinMode, res, commandHandler->commandType()); } catch (const std::exception& error) { - qCritical() << "Command" << std::string(commandType()) + qCritical() << "Command" << commandType() << "fatal error while writing response to stdout:" << error; } @@ -283,8 +283,7 @@ void Controller::onDialogCancel() void Controller::onCriticalFailure(const QString& error) { emit stopCardEventMonitorThread(); - qCritical() << "Exiting due to command" << std::string(commandType()) - << "fatal error:" << error; + qCritical() << "Exiting due to command" << commandType() << "fatal error:" << error; _result = makeErrorObject(RESP_TECH_ERROR, QStringLiteral("Technical error, see application logs")); disposeUI(); diff --git a/src/controller/inputoutputmode.cpp b/src/controller/inputoutputmode.cpp index e84041ad..41f10230 100644 --- a/src/controller/inputoutputmode.cpp +++ b/src/controller/inputoutputmode.cpp @@ -101,6 +101,6 @@ CommandWithArgumentsPtr readCommandFromStdin() "contain a 'command' string and 'arguments' object"); } - return std::make_unique(commandNameToCommandType(command.toString()), + return std::make_unique(CommandType(command.toString()), arguments.toObject().toVariantMap()); } diff --git a/src/controller/logging.hpp b/src/controller/logging.hpp index 60819862..92b43fe2 100644 --- a/src/controller/logging.hpp +++ b/src/controller/logging.hpp @@ -26,17 +26,13 @@ void setupLogging(); -inline QDebug operator<<(QDebug out, const std::string& s) +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) +template +inline QDebug operator<<(QDebug out, const std::basic_string& s) { - out << QString::fromStdString(s); - return out; -} - -inline QDebug operator<<(QDebug out, const std::wstring& s) -{ - out << QString::fromStdWString(s); - return out; + return out << QUtf8StringView(s); } +#endif inline QDebug operator<<(QDebug out, const std::exception& e) { diff --git a/src/controller/qeid.hpp b/src/controller/qeid.hpp index 8ee76fa0..12898eb8 100644 --- a/src/controller/qeid.hpp +++ b/src/controller/qeid.hpp @@ -26,7 +26,6 @@ #include -Q_DECLARE_METATYPE(electronic_id::AutoSelectFailed::Reason) Q_DECLARE_METATYPE(electronic_id::ElectronicID::ptr) Q_DECLARE_METATYPE(std::vector) Q_DECLARE_METATYPE(electronic_id::VerifyPinFailed::Status) diff --git a/src/controller/retriableerror.cpp b/src/controller/retriableerror.cpp deleted file mode 100644 index 9b6c083f..00000000 --- a/src/controller/retriableerror.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2020-2024 Estonian Information System Authority - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "retriableerror.hpp" - -#include -#include "magic_enum/magic_enum.hpp" - -QDebug& operator<<(QDebug& d, const RetriableError e) -{ - return d << QString::fromStdString(std::string(magic_enum::enum_name(e))); -} - -RetriableError toRetriableError(const electronic_id::AutoSelectFailed::Reason reason) -{ - using Reason = electronic_id::AutoSelectFailed::Reason; - - switch (reason) { - case Reason::SERVICE_NOT_RUNNING: - return RetriableError::SMART_CARD_SERVICE_IS_NOT_RUNNING; - case Reason::NO_READERS: - return RetriableError::NO_SMART_CARD_READERS_FOUND; - case Reason::SINGLE_READER_NO_CARD: - case Reason::MULTIPLE_READERS_NO_CARD: - return RetriableError::NO_SMART_CARDS_FOUND; - case Reason::SINGLE_READER_UNSUPPORTED_CARD: - case Reason::MULTIPLE_READERS_NO_SUPPORTED_CARD: - return RetriableError::UNSUPPORTED_CARD; - } - return RetriableError::UNKNOWN_ERROR; -} diff --git a/src/controller/retriableerror.hpp b/src/controller/retriableerror.hpp index 161173b0..11e32538 100644 --- a/src/controller/retriableerror.hpp +++ b/src/controller/retriableerror.hpp @@ -26,92 +26,101 @@ #include "pcsc-cpp/pcsc-cpp-utils.hpp" #include -#include -enum class RetriableError { - // libpcsc-cpp - SMART_CARD_SERVICE_IS_NOT_RUNNING, - NO_SMART_CARD_READERS_FOUND, - NO_SMART_CARDS_FOUND, - FAILED_TO_COMMUNICATE_WITH_CARD_OR_READER, - SMART_CARD_WAS_REMOVED, - SMART_CARD_TRANSACTION_FAILED, - SCARD_ERROR, - // libelectronic-id - SMART_CARD_CHANGE_REQUIRED, - SMART_CARD_COMMAND_ERROR, - PKCS11_TOKEN_NOT_PRESENT, - PKCS11_TOKEN_REMOVED, - PKCS11_ERROR, - // AutoSelectFailed::Reason - UNSUPPORTED_CARD, - // CertificateReader::run - NO_VALID_CERTIFICATE_AVAILABLE, - PIN_VERIFY_DISABLED, - // default - UNKNOWN_ERROR -}; - -Q_DECLARE_METATYPE(RetriableError) +class RetriableError +{ + Q_GADGET +public: + enum Error : quint8 { + // default + UNKNOWN_ERROR, + // libpcsc-cpp + SMART_CARD_SERVICE_IS_NOT_RUNNING, + NO_SMART_CARD_READERS_FOUND, + NO_SMART_CARDS_FOUND, + FAILED_TO_COMMUNICATE_WITH_CARD_OR_READER, + SMART_CARD_WAS_REMOVED, + SMART_CARD_TRANSACTION_FAILED, + SCARD_ERROR, + // libelectronic-id + SMART_CARD_CHANGE_REQUIRED, + SMART_CARD_COMMAND_ERROR, + PKCS11_TOKEN_NOT_PRESENT, + PKCS11_TOKEN_REMOVED, + PKCS11_ERROR, + // AutoSelectFailed::Reason + UNSUPPORTED_CARD, + // CertificateReader::run + NO_VALID_CERTIFICATE_AVAILABLE, + PIN_VERIFY_DISABLED, + }; + Q_ENUM(Error) -QDebug& operator<<(QDebug& d, const RetriableError); - -RetriableError toRetriableError(const electronic_id::AutoSelectFailed::Reason reason); + constexpr RetriableError(Error error = UNKNOWN_ERROR) : value(error) { } + constexpr explicit RetriableError(const electronic_id::AutoSelectFailed::Reason reason) + { + switch (reason) { + using enum electronic_id::AutoSelectFailed::Reason; + case SERVICE_NOT_RUNNING: + value = SMART_CARD_SERVICE_IS_NOT_RUNNING; + break; + case NO_READERS: + value = NO_SMART_CARD_READERS_FOUND; + break; + case SINGLE_READER_NO_CARD: + case MULTIPLE_READERS_NO_CARD: + value = NO_SMART_CARDS_FOUND; + break; + case SINGLE_READER_UNSUPPORTED_CARD: + case MULTIPLE_READERS_NO_SUPPORTED_CARD: + value = UNSUPPORTED_CARD; + break; + default: + value = UNKNOWN_ERROR; + } + } -// Define retriable error handling in one place so that it can be reused. + constexpr operator Error() const { return value; } -#define CATCH_PCSC_CPP_RETRIABLE_ERRORS(ERROR_HANDLER) \ - catch (const pcsc_cpp::ScardServiceNotRunningError& error) \ - { \ - ERROR_HANDLER(RetriableError::SMART_CARD_SERVICE_IS_NOT_RUNNING, error); \ - } \ - catch (const pcsc_cpp::ScardNoReadersError& error) \ - { \ - ERROR_HANDLER(RetriableError::NO_SMART_CARD_READERS_FOUND, error); \ - } \ - catch (const pcsc_cpp::ScardNoCardError& error) \ - { \ - ERROR_HANDLER(RetriableError::NO_SMART_CARDS_FOUND, error); \ - } \ - catch (const pcsc_cpp::ScardCardCommunicationFailedError& error) \ - { \ - ERROR_HANDLER(RetriableError::FAILED_TO_COMMUNICATE_WITH_CARD_OR_READER, error); \ - } \ - catch (const pcsc_cpp::ScardCardRemovedError& error) \ - { \ - ERROR_HANDLER(RetriableError::SMART_CARD_WAS_REMOVED, error); \ - } \ - catch (const pcsc_cpp::ScardTransactionFailedError& error) \ - { \ - ERROR_HANDLER(RetriableError::SMART_CARD_TRANSACTION_FAILED, error); \ - } \ - catch (const pcsc_cpp::ScardError& error) \ - { \ - ERROR_HANDLER(RetriableError::SCARD_ERROR, error); \ + static RetriableError catchRetriableError() + { + try { + throw; + } catch (const pcsc_cpp::ScardServiceNotRunningError& /*error*/) { + return SMART_CARD_SERVICE_IS_NOT_RUNNING; + } catch (const pcsc_cpp::ScardNoReadersError& /*error*/) { + return NO_SMART_CARD_READERS_FOUND; + } catch (const pcsc_cpp::ScardNoCardError& /*error*/) { + return NO_SMART_CARDS_FOUND; + } catch (const pcsc_cpp::ScardCardCommunicationFailedError& /*error*/) { + return FAILED_TO_COMMUNICATE_WITH_CARD_OR_READER; + } catch (const pcsc_cpp::ScardCardRemovedError& /*error*/) { + return SMART_CARD_WAS_REMOVED; + } catch (const pcsc_cpp::ScardTransactionFailedError& /*error*/) { + return SMART_CARD_TRANSACTION_FAILED; + } catch (const pcsc_cpp::ScardError& /*error*/) { + return SCARD_ERROR; + } catch (const electronic_id::SmartCardChangeRequiredError& /*error*/) { + return SMART_CARD_CHANGE_REQUIRED; + } catch (const electronic_id::SmartCardError& /*error*/) { + return SMART_CARD_COMMAND_ERROR; + } catch (const electronic_id::Pkcs11TokenNotPresent& /*error*/) { + return PKCS11_TOKEN_NOT_PRESENT; + } catch (const electronic_id::Pkcs11TokenRemoved& /*error*/) { + return PKCS11_TOKEN_REMOVED; + } catch (const electronic_id::Pkcs11Error& /*error*/) { + return PKCS11_ERROR; + } catch (...) { + return UNKNOWN_ERROR; + } } -#define CATCH_LIBELECTRONIC_ID_RETRIABLE_ERRORS(ERROR_HANDLER) \ - catch (const electronic_id::SmartCardChangeRequiredError& error) \ - { \ - ERROR_HANDLER(RetriableError::SMART_CARD_CHANGE_REQUIRED, error); \ - } \ - catch (const electronic_id::SmartCardError& error) \ - { \ - ERROR_HANDLER(RetriableError::SMART_CARD_COMMAND_ERROR, error); \ - } \ - catch (const electronic_id::Pkcs11TokenNotPresent& error) \ - { \ - ERROR_HANDLER(RetriableError::PKCS11_TOKEN_NOT_PRESENT, error); \ - } \ - catch (const electronic_id::Pkcs11TokenRemoved& error) \ - { \ - ERROR_HANDLER(RetriableError::PKCS11_TOKEN_REMOVED, error); \ - } \ - catch (const electronic_id::Pkcs11Error& error) \ - { \ - ERROR_HANDLER(RetriableError::PKCS11_ERROR, error); \ - } +private: + Error value; +}; #define WARN_RETRIABLE_ERROR(commandType, errorCode, error) \ qWarning().nospace() << "Command " << commandType << " retriable error " << errorCode << ": " \ << error + +Q_DECLARE_METATYPE(RetriableError) diff --git a/src/controller/threads/cardeventmonitorthread.hpp b/src/controller/threads/cardeventmonitorthread.hpp index f088914b..5badb879 100644 --- a/src/controller/threads/cardeventmonitorthread.hpp +++ b/src/controller/threads/cardeventmonitorthread.hpp @@ -32,8 +32,8 @@ class CardEventMonitorThread : public ControllerChildThread using eid_ptr = electronic_id::ElectronicID::ptr; using eid_ptr_vector = std::vector; - CardEventMonitorThread(QObject* parent, std::string commandType) : - ControllerChildThread(std::move(commandType), parent) + CardEventMonitorThread(QObject* parent, CommandType commandType) : + ControllerChildThread(commandType, parent) { } diff --git a/src/controller/threads/controllerchildthread.hpp b/src/controller/threads/controllerchildthread.hpp index a58aa6b4..9bf201c7 100644 --- a/src/controller/threads/controllerchildthread.hpp +++ b/src/controller/threads/controllerchildthread.hpp @@ -23,9 +23,9 @@ #pragma once #include "commandhandler.hpp" -#include "retriableerror.hpp" -#include "qeid.hpp" #include "logging.hpp" +#include "qeid.hpp" +#include "retriableerror.hpp" #include #include @@ -51,11 +51,7 @@ class ControllerChildThread : public QThread } catch (const CommandHandlerVerifyPinFailed& error) { qWarning() << "Command" << commandType() << "PIN verification failed:" << error; - } - CATCH_PCSC_CPP_RETRIABLE_ERRORS(warnAndEmitRetry) - CATCH_LIBELECTRONIC_ID_RETRIABLE_ERRORS(warnAndEmitRetry) - catch (const electronic_id::VerifyPinFailed& error) - { + } catch (const electronic_id::VerifyPinFailed& error) { switch (error.status()) { using enum electronic_id::VerifyPinFailed::Status; case PIN_ENTRY_CANCEL: @@ -76,11 +72,15 @@ class ControllerChildThread : public QThread qCritical() << "Command" << commandType() << "fatal error:" << error; emit failure(error.what()); } - } - catch (const std::exception& error) - { - qCritical() << "Command" << commandType() << "fatal error:" << error; - emit failure(error.what()); + } catch (const std::exception& error) { + if (auto errorCode = RetriableError::catchRetriableError(); + errorCode != RetriableError::UNKNOWN_ERROR) { + WARN_RETRIABLE_ERROR(commandType(), errorCode, error); + emit retry(errorCode); + } else { + qCritical() << "Command" << commandType() << "fatal error:" << error; + emit failure(error.what()); + } } } @@ -88,12 +88,12 @@ class ControllerChildThread : public QThread signals: void cancel(); - void retry(const RetriableError error); + void retry(RetriableError error); void failure(const QString& error); protected: - explicit ControllerChildThread(std::string _cmdType, QObject* parent) noexcept : - QThread(parent), cmdType(std::move(_cmdType)) + explicit ControllerChildThread(CommandType _cmdType, QObject* parent) noexcept : + QThread(parent), cmdType(_cmdType) { // When the thread is finished call deleteLater() on it to free the thread object. Although // the thread objects are freed through the Qt object tree ownership system anyway, it is @@ -104,17 +104,11 @@ class ControllerChildThread : public QThread }); } - const std::string& commandType() const { return cmdType; } + CommandType commandType() const { return cmdType; } static QMutex controllerChildThreadMutex; private: virtual void doRun() = 0; - const std::string cmdType; - - void warnAndEmitRetry(const RetriableError errorCode, const std::exception& error) - { - WARN_RETRIABLE_ERROR(commandType(), errorCode, error); - emit retry(errorCode); - } + const CommandType cmdType; }; diff --git a/src/controller/threads/waitforcardthread.hpp b/src/controller/threads/waitforcardthread.hpp index 7e26eb49..11a37349 100644 --- a/src/controller/threads/waitforcardthread.hpp +++ b/src/controller/threads/waitforcardthread.hpp @@ -36,7 +36,7 @@ class WaitForCardThread : public ControllerChildThread signals: void cardsAvailable(const std::vector& eids); - void statusUpdate(const RetriableError status); + void statusUpdate(RetriableError status); private: void doRun() override @@ -59,22 +59,17 @@ class WaitForCardThread : public ControllerChildThread emit failure(QString(__func__) + ": empty available supported card list"); } } catch (const electronic_id::AutoSelectFailed& failure) { - emit statusUpdate(toRetriableError(failure.reason())); + emit statusUpdate(RetriableError(failure.reason())); return false; - } - CATCH_PCSC_CPP_RETRIABLE_ERRORS(return warnAndEmitStatusUpdate) - CATCH_LIBELECTRONIC_ID_RETRIABLE_ERRORS(return warnAndEmitStatusUpdate) - catch (const std::exception& error) - { + } catch (const std::exception& error) { + if (auto errorCode = RetriableError::catchRetriableError(); + errorCode != RetriableError::UNKNOWN_ERROR) { + WARN_RETRIABLE_ERROR(commandType(), errorCode, error); + emit statusUpdate(errorCode); + return false; + } emit failure(error.what()); } return true; } - - bool warnAndEmitStatusUpdate(const RetriableError errorCode, const std::exception& error) - { - WARN_RETRIABLE_ERROR(commandType(), errorCode, error); - emit statusUpdate(errorCode); - return false; - } }; diff --git a/src/mac/main.mm b/src/mac/main.mm index f2b5b1ed..1d8a82c7 100644 --- a/src/mac/main.mm +++ b/src/mac/main.mm @@ -128,7 +128,7 @@ - (void)notificationEvent:(NSNotification*)notification const auto argumentJson = QJsonDocument::fromJson(QByteArray::fromNSData(req[@"arguments"])); Controller controller(std::make_unique( - commandNameToCommandType(QString::fromNSString(req[@"command"])), + CommandType(QString::fromNSString(req[@"command"])), argumentJson.object().toVariantMap())); controller.run(); QEventLoop e;