diff --git a/CMakeLists.txt b/CMakeLists.txt index b934715..69c0d92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,11 +13,11 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.25) PROJECT(KeyplePluginPcscCppLib - VERSION 2.4.2 + VERSION 2.5.2 LANGUAGES C CXX) SET(CMAKE_PROJECT_VERSION_MAJOR "2") -SET(CMAKE_PROJECT_VERSION_MINOR "4") +SET(CMAKE_PROJECT_VERSION_MINOR "5") SET(CMAKE_PROJECT_VERSION_PATCH "2") SET(CMAKE_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_MAJOR}. diff --git a/include/keyple/plugin/pcsc/PcscCardCommunicationProtocol.hpp b/include/keyple/plugin/pcsc/PcscCardCommunicationProtocol.hpp new file mode 100644 index 0000000..d6fcc80 --- /dev/null +++ b/include/keyple/plugin/pcsc/PcscCardCommunicationProtocol.hpp @@ -0,0 +1,170 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#pragma once + +#include +#include + +namespace keyple { +namespace plugin { +namespace pcsc { + +/** + * List of contactless protocols and technologies identifiable through PC/SC + * readers. + * + *

Each enum value associates a protocol or technology with a specific ATR + * pattern. These patterns follow the PC/SC standard Part 3 for contactless card + * identification. + * + *

The ATR patterns can identify both physical cards and virtual cards + * emulated by NFC devices. + * + *

See PC/SC Workgroup for more + * details. + * + * @since 2.5.0 + */ +class PcscCardCommunicationProtocol { +public: + /** + * Any ISO 14443-4 compliant card or device (both Type A and Type B). + * + *

According to PC/SC specifications, ISO 14443-4 contactless cards have + * a specific ATR structure: + * + *

+ * + *

This structure allows for recognition of both Type A and Type B ISO + * 14443-4 cards, regardless of the number of historical bytes they contain. + * + *

Excludes Innovatron B Prime cards which have their own specific + * category. + * + *

Default rule = + * {@code 3B8[0-9A-F]8001(?!.*5A0A)(?!804F0CA000000306).*} + * + * @since 2.5.0 + */ + static const PcscCardCommunicationProtocol ISO_14443_4; + + /** + * Calypso cards using Innovatron B Prime protocol. + * + *

According to PC/SC Part 3, B Prime cards use a specific ATR format: + * + *

+ * + *

Default rule = {@code 3B8.8001(80)?5A0A.*} + * + * @since 2.5.0 + */ + static const PcscCardCommunicationProtocol INNOVATRON_B_PRIME; + + /** + * NXP MIFARE Ultralight technologies. + * + *

According to PC/SC Part 3 Supplemental Document: + * + *

+ * + *

Default rule = {@code 3B8F8001804F0CA0000003060300030.*} + * + * @since 2.5.0 + */ + static const PcscCardCommunicationProtocol MIFARE_ULTRALIGHT; + + /** + * STMicroelectronics ST25/SRT512 memory tags. + * + *

According to PC/SC Part 3 Supplemental Document: + * + *

+ * + *

Default rule = {@code 3B8F8001804F0CA0000003060(5|6|7)0007.*} + * + * @since 2.5.0 + */ + static const PcscCardCommunicationProtocol ST25_SRT512; + + /** + * ISO 7816-3 Card (contact communication protocol) + * + *

Default rule = {@code 3.*} + * + * @since 2.5.0 + */ + static const PcscCardCommunicationProtocol ISO_7816_3; + + /** + * + */ + const std::string& + getName() const; + + /** + * (private-package)
+ * Gets the default rule associated to the protocol. + * + * @return The regular expression pattern as a String + * @since 2.0.0 + */ + const std::string& + getDefaultRule() const; + +private: + /** + * + */ + const std::string mName; + + /** + * + */ + const std::string mDefaultRule; + + /** + * Constructor + * + * @param name The protocol name. + * @param defaultRule The default rule as a regular expression pattern. + */ + PcscCardCommunicationProtocol( + const std::string& name, const std::string& defaultRule); +}; + +} /* namespace pcsc */ +} /* namespace plugin */ +} /* namespace keyple */ diff --git a/include/keyple/plugin/pcsc/PcscPlugin.hpp b/include/keyple/plugin/pcsc/PcscPlugin.hpp index 35b6fe9..439f5e8 100644 --- a/include/keyple/plugin/pcsc/PcscPlugin.hpp +++ b/include/keyple/plugin/pcsc/PcscPlugin.hpp @@ -34,6 +34,6 @@ class PcscPlugin : public KeyplePluginExtension { virtual ~PcscPlugin() = default; }; -} -} -} +} /* namespace pcsc */ +} /* namespace plugin */ +} /* namespace keyple */ diff --git a/include/keyple/plugin/pcsc/PcscPluginAdapter.hpp b/include/keyple/plugin/pcsc/PcscPluginAdapter.hpp index bc4c9e1..e224df2 100644 --- a/include/keyple/plugin/pcsc/PcscPluginAdapter.hpp +++ b/include/keyple/plugin/pcsc/PcscPluginAdapter.hpp @@ -26,6 +26,7 @@ #include "keyple/plugin/pcsc/PcscPlugin.hpp" #include "keyple/plugin/pcsc/PcscReaderAdapter.hpp" #include "keyple/plugin/pcsc/cpp/CardTerminal.hpp" +#include "keyple/plugin/pcsc/cpp/CardTerminals.hpp" namespace keyple { namespace plugin { @@ -37,6 +38,7 @@ using keyple::core::util::cpp::Logger; using keyple::core::util::cpp::LoggerFactory; using keyple::core::util::cpp::Pattern; using keyple::plugin::pcsc::cpp::CardTerminal; +using keyple::plugin::pcsc::cpp::CardTerminals; class PcscReaderAdapter; @@ -215,6 +217,11 @@ class PcscPluginAdapter final */ std::map mProtocolRulesMap; + /** + * + */ + std::shared_ptr mTerminals; + /** * */ diff --git a/include/keyple/plugin/pcsc/PcscPluginFactoryBuilder.hpp b/include/keyple/plugin/pcsc/PcscPluginFactoryBuilder.hpp index 8d34494..33ed4a0 100644 --- a/include/keyple/plugin/pcsc/PcscPluginFactoryBuilder.hpp +++ b/include/keyple/plugin/pcsc/PcscPluginFactoryBuilder.hpp @@ -35,15 +35,14 @@ using keyple::core::util::cpp::Pattern; * method PcscReader#setContactless(bool).
* A set of default protocol identification rules is also proposed. * - * @see PcscSupportedContactProtocol - * @see PcscSupportedContactlessProtocol + * @see PcscCardCommunicationProtocol * @since 2.0.0 */ class KEYPLEPLUGINPCSC_API PcscPluginFactoryBuilder final { public: /** - * Builder to build a {@link PcscPluginFactory}. + * Builder to build a PcscPluginFactory. * * @since 2.0.0 */ diff --git a/include/keyple/plugin/pcsc/PcscReader.hpp b/include/keyple/plugin/pcsc/PcscReader.hpp index 3e68772..10efce2 100644 --- a/include/keyple/plugin/pcsc/PcscReader.hpp +++ b/include/keyple/plugin/pcsc/PcscReader.hpp @@ -140,18 +140,49 @@ class KEYPLEPLUGINPCSC_API PcscReader : public KeypleReaderExtension { */ enum class DisconnectionMode { /** - * Resets the card + * Resets the card. This sends a reset signal to the card while keeping + * the connection alive. * + *

Corresponds to PC/SC `SCARD_RESET_CARD`. * @since 2.0.0 */ RESET, /** - * Keeps the status of the card unchanged + * Leaves the card in its current state without performing any reset or + * power down. + * + *

Corresponds to PC/SC `SCARD_LEAVE_CARD`. * * @since 2.0.0 */ - LEAVE + LEAVE, + + /** + * Completely powers off the card. + * + *

Corresponds to PC/SC `SCARD_UNPOWER_CARD`. + * + *

This mode is only available with the default security provider. + * Depending on the provider used, a runtime error may occur during + * eader enumeration. + * + * @since 2.5.0 + */ + UNPOWER, + + /** + * Ejects the card (if supported by the reader). + * + *

Corresponds to PC/SC `SCARD_EJECT_CARD`. + * + *

This mode is only available with the default security provider. + * Depending on the provider used, a runtime error may occur during + * reader enumeration. + * + * @since 2.5.0 + */ + EJECT }; /** @@ -274,6 +305,25 @@ class KEYPLEPLUGINPCSC_API PcscReader : public KeypleReaderExtension { friend std::ostream& operator<<(std::ostream& os, const DisconnectionMode dm); }; +/** + * + */ +inline std::string operator+(std::string str, const PcscReader::DisconnectionMode dm) +{ + switch (dm) { + case PcscReader::DisconnectionMode::RESET: + return str + "RESET"; + case PcscReader::DisconnectionMode::LEAVE: + return str + "LEAVE"; + case PcscReader::DisconnectionMode::UNPOWER: + return str + "UNPOWER"; + case PcscReader::DisconnectionMode::EJECT: + return str + "EJECT"; + default: + return str + "UNKNOWN"; + } +} + } /* namespace pcsc */ } /* namespace plugin */ } /* namespace keyple */ \ No newline at end of file diff --git a/include/keyple/plugin/pcsc/PcscReaderAdapter.hpp b/include/keyple/plugin/pcsc/PcscReaderAdapter.hpp index 3f54128..79fce3e 100644 --- a/include/keyple/plugin/pcsc/PcscReaderAdapter.hpp +++ b/include/keyple/plugin/pcsc/PcscReaderAdapter.hpp @@ -25,6 +25,8 @@ #include "keyple/core/util/cpp/Logger.hpp" #include "keyple/core/util/cpp/LoggerFactory.hpp" #include "keyple/plugin/pcsc/PcscReader.hpp" +#include "keyple/plugin/pcsc/cpp/Card.hpp" +#include "keyple/plugin/pcsc/cpp/CardChannel.hpp" #include "keyple/plugin/pcsc/cpp/CardTerminal.hpp" namespace keyple { @@ -38,8 +40,12 @@ using keyple::core::plugin::spi::reader::observable::state::processing::CardPres using keyple::core::plugin::spi::reader::observable::state::removal::CardRemovalWaiterBlockingSpi; using keyple::core::util::cpp::Logger; using keyple::core::util::cpp::LoggerFactory; +using keyple::plugin::pcsc::cpp::Card; +using keyple::plugin::pcsc::cpp::CardChannel; using keyple::plugin::pcsc::cpp::CardTerminal; +using DisconnectionMode = PcscReader::DisconnectionMode; + class PcscPluginAdapter; /** @@ -68,28 +74,32 @@ class PcscReaderAdapter final * * @since 2.0.0 */ - void waitForCardInsertion() override; + void + waitForCardInsertion() override; /** * {@inheritDoc} * * @since 2.0.0 */ - void stopWaitForCardInsertion() override; + void + stopWaitForCardInsertion() override; /** * {@inheritDoc} * * @since 2.0.0 */ - bool isProtocolSupported(const std::string& readerProtocol) const final; + bool + isProtocolSupported(const std::string& readerProtocol) const final; /** * {@inheritDoc} * * @since 2.0.0 */ - void activateProtocol(const std::string& readerProtocol) override; + void + activateProtocol(const std::string& readerProtocol) override; /** @@ -97,7 +107,8 @@ class PcscReaderAdapter final * * @since 2.0.0 */ - void deactivateProtocol(const std::string& readerProtocol) final; + void + deactivateProtocol(const std::string& readerProtocol) final; /** * {@inheritDoc} @@ -105,112 +116,128 @@ class PcscReaderAdapter final * @throws PatternSyntaxException If the expression's syntax is invalid * @since 2.0.0 */ - bool isCurrentProtocol(const std::string& readerProtocol) const final; + bool + isCurrentProtocol(const std::string& readerProtocol) const final; /** * {@inheritDoc} * * @since 2.0.0 */ - void onStartDetection() final; + void + onStartDetection() final; /** * {@inheritDoc} * * @since 2.0.0 */ - void onStopDetection() final; + void + onStopDetection() final; /** * {@inheritDoc} * * @since 2.0.0 */ - const std::string& getName() const final; + const std::string& + getName() const final; /** * {@inheritDoc} * * @since 2.0.0 */ - void openPhysicalChannel() final; + void + openPhysicalChannel() final; /** * {@inheritDoc} * * @since 2.0.0 */ - void closePhysicalChannel() final; + void + closePhysicalChannel() final; /** * {@inheritDoc} * * @since 2.0.0 */ - bool isPhysicalChannelOpen() const final; + bool + isPhysicalChannelOpen() const final; /** * {@inheritDoc} * * @since 2.0.0 */ - bool checkCardPresence() final; + bool + checkCardPresence() final; /** * {@inheritDoc} * * @since 2.0.0 */ - const std::string getPowerOnData() const final; + const std::string + getPowerOnData() const final; /** * {@inheritDoc} * * @since 2.0.0 */ - const std::vector transmitApdu(const std::vector& apduCommandData) final; + const std::vector + transmitApdu(const std::vector& apduCommandData) final; /** * {@inheritDoc} * * @since 2.0.0 */ - bool isContactless() final; + bool + isContactless() final; /** * {@inheritDoc} * * @since 2.0.0 */ - void onUnregister() final; + void + onUnregister() final; /** * {@inheritDoc} * * @since 2.0.0 */ - void monitorCardPresenceDuringProcessing() override; + void + monitorCardPresenceDuringProcessing() override; /** * {@inheritDoc} * * @since 2.0.0 */ - void stopCardPresenceMonitoringDuringProcessing() override; + void + stopCardPresenceMonitoringDuringProcessing() override; /** * {@inheritDoc} * * @since 2.0.0 */ - void waitForCardRemoval() final; + void + waitForCardRemoval() final; /** * {@inheritDoc} * * @since 2.0.0 */ - void stopWaitForCardRemoval() final; + void + stopWaitForCardRemoval() final; /** * {@inheritDoc} @@ -219,14 +246,16 @@ class PcscReaderAdapter final * * @since 2.0.0 */ - PcscReader& setSharingMode(const SharingMode sharingMode) final; + PcscReader& + setSharingMode(const SharingMode sharingMode) final; /** * {@inheritDoc} * * @since 2.0.0 */ - PcscReader& setContactless(const bool contactless) final; + PcscReader& + setContactless(const bool contactless) final; /** * {@inheritDoc} @@ -235,7 +264,8 @@ class PcscReaderAdapter final * * @since 2.0.0 */ - PcscReader& setIsoProtocol(const IsoProtocol& isoProtocol) final; + PcscReader& + setIsoProtocol(const IsoProtocol& isoProtocol) final; /** * {@inheritDoc} @@ -244,22 +274,25 @@ class PcscReaderAdapter final * * @since 2.0.0 */ - PcscReader& setDisconnectionMode(const DisconnectionMode disconnectionMode) final; + PcscReader& + setDisconnectionMode(const DisconnectionMode disconnectionMode) final; /** * {@inheritDoc} * * @since 2.1.0 */ - const std::vector transmitControlCommand(const int commandId, - const std::vector& command) override; + const std::vector + transmitControlCommand( + const int commandId, const std::vector& command) override; /** * {@inheritDoc} * * @since 2.1.0 */ - int getIoctlCcidEscapeCommandId() const override; + int + getIoctlCcidEscapeCommandId() const override; private: /** @@ -297,6 +330,21 @@ class PcscReaderAdapter final */ const int mCardMonitoringCycleDuration; + /** + * + */ + const std::vector mPingApdu; + + /** + * + */ + std::shared_ptr mCard; + + /** + * + */ + std::shared_ptr mChannel; + /** * */ @@ -335,13 +383,74 @@ class PcscReaderAdapter final /** * */ - void closePhysicalChannelSafely(); + bool mIsObservationActive; + /** * */ - void resetContext(); + void + closePhysicalChannelSafely(); + /** + * + */ + void + resetContext(); + + /** + * Disconnects the current card and resets the context and reader state. + * + *

This method handles the disconnection of a card, taking into account + * the specific disconnection mode. If the card is an instance of JnaCard, it + * disconnects using the extended mode specified by + * getDisposition(DisconnectionMode)} and resets the reader state to avoid + * incorrect card detection in subsequent operations. For other card types, + * it disconnects using the specified disconnection mode directly. + * + *

If a CardException occurs during the operation, a ReaderIOException is + * thrown with the associated error message. + * + *

Once the disconnection is handled, the method ensures that the context + * is reset. + * + * @throw ReaderIOException If an error occurs while closing the physical + * channel. + */ + void + disconnect(); + + /** + * Maps a DisconnectionMode to the corresponding SCARD_* constant. + * + * @param mode The disconnection mode. + * @return The corresponding SCARD_* value. + */ + static int getDisposition(const DisconnectionMode mode); + + /** + * Resets the state of the card reader. + * + *

This method attempts to reset the reader state based on the current + * disconnection mode. If the disconnection mode is set to UNPOWER, it + * reconnects to the terminal and then disconnects without powering off the + * reader. If any {@link CardException} occurs during this process, it is + * handled silently. + */ + void + resetReaderState(); + + /** + * + */ + void + waitForCardRemovalByPolling(); + + /** + * + */ + void + waitForCardRemovalStandard(); }; } /* namespace pcsc */ diff --git a/include/keyple/plugin/pcsc/PcscSupportedContactProtocol.hpp b/include/keyple/plugin/pcsc/PcscSupportedContactProtocol.hpp index 79c6725..79e2def 100644 --- a/include/keyple/plugin/pcsc/PcscSupportedContactProtocol.hpp +++ b/include/keyple/plugin/pcsc/PcscSupportedContactProtocol.hpp @@ -15,7 +15,7 @@ #include - /* Keyple Plugin Pcsc */ +/* Keyple Plugin Pcsc */ #include "keyple/plugin/pcsc/KeyplePluginPcscExport.hpp" namespace keyple { @@ -28,6 +28,7 @@ namespace pcsc { *

TODO Improve protocol identification * * @since 2.0.0 + * @deprecated */ class KEYPLEPLUGINPCSC_API PcscSupportedContactProtocol { public: @@ -86,7 +87,8 @@ class KEYPLEPLUGINPCSC_API PcscSupportedContactProtocol { * @param defaultRule The default rule. * @since 2.0.0 */ - PcscSupportedContactProtocol(const std::string& name, const std::string& defaultRule); + PcscSupportedContactProtocol( + const std::string& name, const std::string& defaultRule); }; } diff --git a/include/keyple/plugin/pcsc/PcscSupportedContactlessProtocol.hpp b/include/keyple/plugin/pcsc/PcscSupportedContactlessProtocol.hpp index 299f8b7..24e7085 100644 --- a/include/keyple/plugin/pcsc/PcscSupportedContactlessProtocol.hpp +++ b/include/keyple/plugin/pcsc/PcscSupportedContactlessProtocol.hpp @@ -39,6 +39,7 @@ namespace pcsc { * See PC/SC Workgroup * * @since 2.0.0 + * @deprecated */ class KEYPLEPLUGINPCSC_API PcscSupportedContactlessProtocol { public: @@ -120,10 +121,12 @@ class KEYPLEPLUGINPCSC_API PcscSupportedContactlessProtocol { /** * Constructor * + * @param name The protocol name.. * @param defaultRule The default rule. * @since 2.0.0 */ - PcscSupportedContactlessProtocol(const std::string& name, const std::string& defaultRule); + PcscSupportedContactlessProtocol( + const std::string& name, const std::string& defaultRule); }; } diff --git a/include/keyple/plugin/pcsc/cpp/Card.hpp b/include/keyple/plugin/pcsc/cpp/Card.hpp new file mode 100644 index 0000000..93493c2 --- /dev/null +++ b/include/keyple/plugin/pcsc/cpp/Card.hpp @@ -0,0 +1,178 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#include +#endif + +#include "keyple/plugin/pcsc/cpp/CardChannel.hpp" +#include "keyple/plugin/pcsc/cpp/CardChannel.hpp" +#include "keyple/core/util/cpp/Logger.hpp" +#include "keyple/core/util/cpp/LoggerFactory.hpp" + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { + +using keyple::core::util::cpp::Logger; +using keyple::core::util::cpp::LoggerFactory; + +class CardTerminal; + +/** + * A Smart Card with which a connection has been established. Card objects are + * obtained by calling CardTerminal.connect(). + */ +class Card : public std::enable_shared_from_this { +public: + /** + * + */ + DWORD mProtocol; + + /** + * + */ + const SCARD_IO_REQUEST mIORequest; + + /** + * + */ + const SCARDHANDLE mHandle; + + /** + * Constructor. + */ + Card( + const std::shared_ptr cardTerminal, + const SCARDHANDLE handle, + const std::vector atr, + const DWORD protocol, + const SCARD_IO_REQUEST ioRequest); + + /** + * Destructor. + */ + virtual ~Card() = default; + + /** + * Disconnects the connection with this card. After this method returns, + * calling methods on this object or in CardChannels associated with this + * object that require interaction with the card will raise a + * IllegalStateException. + * + * @param reset Whether to reset the card after disconnecting. + * @throw CardException If the card operation failed. + * @throw SecurityException If a SecurityManager exists and the caller doe + * not have the required permission. + */ + void + disconnect(const bool reset); + + /** + * Returns the ATR of this card. + * + * @return the ATR of this card. + */ + const std::vector& + getATR() const; + + /** + * Requests exclusive access to this card. + * + * Once a thread has invoked beginExclusive, only this thread is allowed to + * communicate with this card until it calls endExclusive. Other threads + * attempting communication will receive a CardException. + * + * @throw CardException If exclusive access has already been set or if + * exclusive access could not be established + * @throw IllegalStateException If this card object has been disposed of via + * the disconnect() method + */ + void + beginExclusive(); + + /** + * Releases the exclusive access previously established using + * beginExclusive. + * + * @throw CardException If the CardException - if the + * @throw IllegalStateException If the active Thread does not currently have + * exclusive access to this card or if this card object has been disposed of + * via the disconnect() method + */ + void + endExclusive(); + + /** + * Returns the protocol in use for this card. + */ + const std::string + getProtocol() const; + + /** + * Returns the CardChannel for the basic logical channel. The basic logical + * channel has a channel number of 0. + * + * @throw IllegalStateException If this card object has been disposed of via + * the disconnect() method + */ + std::shared_ptr + getBasicChannel(); + + /** + *Transmits a control command to the terminal device. + * + * @param controlCode The control code of the command + * @param command The command data + * @throw CardException If the card operation failed + * @throw IllegalStateException If this card object has been disposed of via + * the disconnect() method + */ + virtual const std::vector + transmitControlCommand( + const int commandId, const std::vector& command); + +private: + /** + * + */ + const std::shared_ptr mLogger = + LoggerFactory::getLogger(typeid(Card)); + + /** + * + */ + std::vector mAtr; + + /** + * + */ + const std::shared_ptr mCardTerminal; +}; + +} /* namespace cpp */ +} /* namespace pcsc*/ +} /* namespace pcsc */ +} /* namespace plugin */ \ No newline at end of file diff --git a/include/keyple/plugin/pcsc/cpp/CardChannel.hpp b/include/keyple/plugin/pcsc/cpp/CardChannel.hpp new file mode 100644 index 0000000..c7f028c --- /dev/null +++ b/include/keyple/plugin/pcsc/cpp/CardChannel.hpp @@ -0,0 +1,124 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#pragma once + +#include +#include +#include + +#if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#include +#endif + +#include "keyple/core/util/cpp/Logger.hpp" +#include "keyple/core/util/cpp/LoggerFactory.hpp" + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { + +using keyple::core::util::cpp::Logger; +using keyple::core::util::cpp::LoggerFactory; + +class Card; + +/** + * A logical channel connection to a Smart Card. It is used to exchange APDUs + * with a Smart Card. A CardChannel object can be obtained by calling the method + * Card::getBasicChannel() or Card::openLogicalChannel(). + */ +class CardChannel { +public: + /** + * + */ + CardChannel(const std::shared_ptr card, const int channel); + + /** + * Returns the Card this channel is associated with. + * + * @return The Card this channel is associated with. + */ + std::shared_ptr + getCard() const; + + /** + * Returns the channel number of this CardChannel. A channel number of 0 + * indicates the basic logical channel. + * + * @throw IllegalStateException If this channel has been closed or if the + * corresponding Card has been disconnected. + * @return The channel number of this CardChannel. + */ + int + getChannelNumber() const; + + /** + * Closes this CardChannel. The logical channel is closed by issuing a + * MANAGE CHANNEL command that should use the format [xx 70 80 0n] where n + * is the channel number of this channel and xx is the CLA byte that encodes + * this logical channel and has all other bits set to 0. After this method + * returns, calling other methods in this class will raise an + * IllegalStateException. + * + * Note that the basic logical channel cannot be closed using this method. + * It can be closed by calling Card.disconnect(boolean). + * + * @throw CardException If the card operation failed. + * @throw IllegalStateException If this CardChannel represents a connection + * the basic logical channel + */ + void + close(); + + /** + * Transmits the command APDU stored in the command apduIn and receives + * the response APDU in the response. + * + * @param apduIn C-APDU + * @return R-APDU in the form of a byte vector. + */ + std::vector transmit(const std::vector& apduIn); + +private: + /** + * + */ + const std::shared_ptr mLogger = + LoggerFactory::getLogger(typeid(CardChannel)); + + /** + * + */ + int mChannel; + + /** + * + */ + bool mIsClosed; + + /** + * + */ + std::shared_ptr mCard; +}; + +} /* namespace cpp */ +} /* namespace pcsc*/ +} /* namespace pcsc */ +} /* namespace plugin */ \ No newline at end of file diff --git a/include/keyple/plugin/pcsc/cpp/CardTerminal.hpp b/include/keyple/plugin/pcsc/cpp/CardTerminal.hpp index 206c998..7518607 100644 --- a/include/keyple/plugin/pcsc/cpp/CardTerminal.hpp +++ b/include/keyple/plugin/pcsc/cpp/CardTerminal.hpp @@ -24,6 +24,7 @@ #include "keyple/core/util/cpp/LoggerFactory.hpp" #include "keyple/plugin/pcsc/KeyplePluginPcscExport.hpp" #include "keyple/plugin/pcsc/PcscReader.hpp" +#include "keyple/plugin/pcsc/cpp/Card.hpp" namespace keyple { namespace plugin { @@ -35,12 +36,16 @@ using keyple::core::util::cpp::LoggerFactory; using DisconnectionMode = PcscReader::DisconnectionMode; -class KEYPLEPLUGINPCSC_API CardTerminal { +class CardTerminals; + +class KEYPLEPLUGINPCSC_API CardTerminal +: public std::enable_shared_from_this { public: /** * */ - explicit CardTerminal(const std::string& name); + explicit CardTerminal( + std::shared_ptr cardTerminals, const std::string& name); /** * @@ -48,80 +53,87 @@ class KEYPLEPLUGINPCSC_API CardTerminal { virtual ~CardTerminal() = default; /** + * Returns the unique name of this terminal. * + * @return the unique name of this terminal. */ - const std::string& getName() const; - - /** - * - */ - bool isCardPresent(bool release); - - /** - * - */ - bool isConnected(); - - /** - * - */ - void openAndConnect(const std::string& protocol); - - /** - * - */ - void closeAndDisconnect(const DisconnectionMode mode); - - /** - * - */ - static const std::vector& listTerminals(); + const std::string& + getName() const; /** + * Returns whether a card is present in this terminal. * + * @return whether a card is present in this terminal. + * @throw CardException if the status could not be determined. */ - const std::vector& getATR(); + bool + isCardPresent(); /** + * Establishes a connection to the card. If a connection has previously + * established using the specified protocol, this method returns the same + * Card object as the previous call. * + * @param protocol The protocol to use ("T=0", "T=1", or "T=CL"), or "*" to + * connect using any available protocol. + * @throw IllegalArgumentException if protocol is an invalid protocol + * specification. + * @throw CardNotPresentException If no card is present in this terminal. + * @throw CardException If a connection could not be established using the + * specified protocol or if a connection has previously been established + * using a different protocol. + * @throw SecurityException If a SecurityManager exists and the caller does + * not have the required permission. */ - virtual const std::vector transmitControlCommand(const int commandId, - const std::vector& command); + std::shared_ptr connect(const std::string& protocol); /** + * Waits until a card is absent in this terminal or the timeout + * expires. If the method returns due to an expired timeout, it returns + * false. Otherwise it return true. * - */ - std::vector transmitApdu(const std::vector& apduIn); - - /** - * - */ - void beginExclusive(); - - /** + *

If no card is present in this terminal when this + * method is called, it returns immediately. * + * @param timeout if positive, block for up to timeout + * milliseconds; if zero, block indefinitely; must not be negative + * @return false if the method returns due to an expired timeout, + * true otherwise. + * @throw IllegalArgumentException if timeout is negative + * @throw CardException if the operation failed */ - void endExclusive(); + bool + waitForCardAbsent(uint64_t timeout); /** + * Waits until a card is present in this terminal or the timeout + * expires. If the method returns due to an expired timeout, it returns + * false. Otherwise it return true. * - */ - bool waitForCardAbsent(uint64_t timeout); - - /** + *

If a card is present in this terminal when this + * method is called, it returns immediately. * + * @param timeout if positive, block for up to timeout + * milliseconds; if zero, block indefinitely; must not be negative + * @return false if the method returns due to an expired timeout, + * true otherwise. + * @throw IllegalArgumentException if timeout is negative + * @throw CardException if the operation failed */ - bool waitForCardPresent(uint64_t timeout); + bool + waitForCardPresent(uint64_t timeout); /** * */ - friend std::ostream& operator<<(std::ostream& os, const CardTerminal& t); + friend std::ostream& + operator<<(std::ostream& os, const CardTerminal& t); /** * */ - friend std::ostream& operator<<(std::ostream& os, const std::vector& vt); + friend std::ostream& + operator<<(std::ostream& os, const std::vector& vt); /** * @@ -140,31 +152,6 @@ class KEYPLEPLUGINPCSC_API CardTerminal { const std::shared_ptr mLogger = LoggerFactory::getLogger(typeid(CardTerminal)); - /** - * - */ - SCARDCONTEXT mContext; - - /** - * - */ - SCARDHANDLE mHandle; - - /** - * - */ - DWORD mProtocol; - - /** - * - */ - SCARD_IO_REQUEST mPioSendPCI; - - /** - * - */ - DWORD mState; - /** * */ @@ -173,35 +160,10 @@ class KEYPLEPLUGINPCSC_API CardTerminal { /** * */ - std::vector mAtr; - - /** - * - */ - bool mContextEstablished; - - /** - * - */ - void establishContext(); - - /** - * - */ - void releaseContext(); - - /** - * - */ - bool connect(); - - /** - * - */ - void disconnect(); + const std::shared_ptr mCardTerminals; }; -} -} -} -} +} /* namespace cpp */ +} /* namespace pcsc*/ +} /* namespace pcsc */ +} /* namespace plugin */ diff --git a/include/keyple/plugin/pcsc/cpp/CardTerminals.hpp b/include/keyple/plugin/pcsc/cpp/CardTerminals.hpp new file mode 100644 index 0000000..5b1bafd --- /dev/null +++ b/include/keyple/plugin/pcsc/cpp/CardTerminals.hpp @@ -0,0 +1,159 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#pragma once + +#include +#include + +#include "keyple/plugin/pcsc/cpp/CardTerminal.hpp" + +#if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#include +#endif + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { + +/** + * The set of terminals supported by a TerminalFactory. + * This class allows applications to enumerate the available CardTerminals, + * obtain a specific CardTerminal, or wait for the insertion or removal of + * cards. + */ +class CardTerminals : public std::enable_shared_from_this { +public: + /** + * + */ + SCARDCONTEXT& mContext; + + /** + * Enumeration of attributes of a CardTerminal. + * It is used as a parameter to the {@linkplain CardTerminals#list} method. + * + * @since 1.6 + */ + enum class State { + /** + * All CardTerminals. + */ + ALL, + + /** + * CardTerminals in which a card is present. + */ + CARD_PRESENT, + + /** + * CardTerminals in which a card is not present. + */ + CARD_ABSENT, + + /** + * CardTerminals for which a card insertion was detected during the + * latest call to waitForChange() + */ + CARD_INSERTION, + + /** + * CardTerminals for which a card removal was detected during the + * latest call to waitForChange(). + */ + CARD_REMOVAL + }; + + /** + * Constructs. + */ + CardTerminals(SCARDCONTEXT& context); + + /** + * Returns an unmodifiable list of all available terminals. + * + * @return an unmodifiable list of all available terminals. + * @throw CardException if the card operation failed + */ + const std::vector> list(); + + /** + * Returns an unmodifiable list of all terminals matching the specified + * state. + * + * @param state the State + * @return an unmodifiable list of all terminals matching the specified + * attribute. + * @throw NullPointerException if attr is null + * @throw CardException if the card operation failed + */ + const std::vector> list(const State state); + + /** + * Returns the terminal with the specified name or null if no such + * terminal exists. + * + * @return the terminal with the specified name or null if no such + * terminal exists. + * + * @throws NullPointerException if name is null + */ + std::shared_ptr getTerminal(const std::string& name); + + /** + * Waits for card insertion or removal in any of the terminals of this + * object. + * + * @throw IllegalStateException if this CardTerminals + * object does not contain any terminals + * @throw CardException if the card operation failed + */ + void + waitForChange(); + + /** + * Waits for card insertion or removal in any of the terminals of this + * object or until the timeout expires. + * + * @param timeout if positive, block for up to timeout + * milliseconds; if zero, block indefinitely; must not be negative + * @return false if the method returns due to an expired timeout, + * true otherwise. + * @throw IllegalStateException if this CardTerminals + * object does not contain any terminals + * @throw IllegalArgumentException if timeout is negative + * @throw CardException if the card operation failed + */ + bool + waitForChange(long timeout); + +private: + /** + * + */ + std::vector mKnownReaders; + + /** + * + */ + std::vector mZombieReaders; +}; + +} /* namespace cpp */ +} /* namespace pcsc*/ +} /* namespace pcsc */ +} /* namespace plugin */ diff --git a/include/keyple/plugin/pcsc/cpp/TerminalFactory.hpp b/include/keyple/plugin/pcsc/cpp/TerminalFactory.hpp new file mode 100644 index 0000000..5a3c769 --- /dev/null +++ b/include/keyple/plugin/pcsc/cpp/TerminalFactory.hpp @@ -0,0 +1,94 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#pragma once + +#include +#include +#include + +#include "keyple/plugin/pcsc/cpp/CardTerminals.hpp" + +#if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#include +#endif + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { + +/** + * A factory for CardTerminal objects. + */ +class TerminalFactory { +public: + /** + * Singletons should not be cloneable. + */ + TerminalFactory(TerminalFactory& other) = delete; + + /** + * + */ + virtual ~TerminalFactory() = default; + + /** + * Singletons should not be assignable. + */ + void operator=(const TerminalFactory&) = delete; + + /** + * + */ + static std::shared_ptr getDefault(); + + /** + * Returns a new CardTerminals object encapsulating the terminals + * supported by this factory. + * + * @return a new CardTerminals object encapsulating the terminals + * supported by this factory. + */ + std::shared_ptr terminals(); + +private: + /** + * + */ + SCARDCONTEXT mContext; + + /** + * + */ + static std::shared_ptr mInstance; + + /** + * + */ + TerminalFactory() = default; + + /** + * + */ + const std::vector + listTerminals(); +}; + +} /* namespace cpp */ +} /* namespace pcsc*/ +} /* namespace pcsc */ +} /* namespace plugin */ \ No newline at end of file diff --git a/include/keyple/plugin/pcsc/cpp/exception/CardNotPresentException.hpp b/include/keyple/plugin/pcsc/cpp/exception/CardNotPresentException.hpp new file mode 100644 index 0000000..02efed4 --- /dev/null +++ b/include/keyple/plugin/pcsc/cpp/exception/CardNotPresentException.hpp @@ -0,0 +1,45 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#pragma once + +#include "keyple/plugin/pcsc/cpp/exception/CardException.hpp" + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { +namespace exception { + + +class CardNotPresentException : public CardException { +public: + /** + * + */ + explicit CardNotPresentException(const std::string& msg) + : CardException(msg) {} + + /** + * + */ + CardNotPresentException( + const std::string& msg, const std::shared_ptr cause) + : CardException(msg, cause) {} +}; + +} +} +} +} +} diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index b1f879e..19451f2 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -26,6 +26,7 @@ ADD_LIBRARY( ${LIBRARY_TYPE} + ${CMAKE_CURRENT_SOURCE_DIR}/PcscCardCommunicationProtocol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PcscPluginAdapter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PcscPluginFactoryAdapter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PcscPluginFactoryBuilder.cpp @@ -33,7 +34,11 @@ ADD_LIBRARY( ${CMAKE_CURRENT_SOURCE_DIR}/PcscReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PcscSupportedContactProtocol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PcscSupportedContactlessProtocol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/Card.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/CardChannel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/CardTerminal.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/CardTerminals.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/TerminalFactory.cpp ) TARGET_INCLUDE_DIRECTORIES( diff --git a/src/main/PcscCardCommunicationProtocol.cpp b/src/main/PcscCardCommunicationProtocol.cpp new file mode 100644 index 0000000..5b05bff --- /dev/null +++ b/src/main/PcscCardCommunicationProtocol.cpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#include "keyple/plugin/pcsc/PcscCardCommunicationProtocol.hpp" + +namespace keyple { +namespace plugin { +namespace pcsc { + +const PcscCardCommunicationProtocol + PcscCardCommunicationProtocol::ISO_14443_4( + "ISO_14443_4", "3B8[0-9A-F]8001(?!.*5A0A)(?!804F0CA000000306).*"); + +const PcscCardCommunicationProtocol + PcscCardCommunicationProtocol::INNOVATRON_B_PRIME( + "INNOVATRON_B_PRIME", "3B8.8001(80)?5A0A.*"); + +const PcscCardCommunicationProtocol + PcscCardCommunicationProtocol::MIFARE_ULTRALIGHT( + "MIFARE_ULTRALIGHT", "3B8F8001804F0CA0000003060300030.*"); + +const PcscCardCommunicationProtocol + PcscCardCommunicationProtocol::ST25_SRT512( + "ST25_SRT512", "3B8F8001804F0CA0000003060(5|6|7)0007.*"); + +const PcscCardCommunicationProtocol + PcscCardCommunicationProtocol::ISO_7816_3("ISO_7816_3", "3.*"); + +PcscCardCommunicationProtocol::PcscCardCommunicationProtocol( + const std::string& name, const std::string& defaultRule) +: mName(name) +, mDefaultRule(defaultRule) +{ +} + +const std::string& +PcscCardCommunicationProtocol::getDefaultRule() const +{ + return mDefaultRule; +} + +const std::string& +PcscCardCommunicationProtocol::getName() const +{ + return mName; +} + +} /* namespace pcsc */ +} /* namespace plugin */ +} /* namespace keyple */ \ No newline at end of file diff --git a/src/main/PcscPluginAdapter.cpp b/src/main/PcscPluginAdapter.cpp index ebdb631..887659c 100644 --- a/src/main/PcscPluginAdapter.cpp +++ b/src/main/PcscPluginAdapter.cpp @@ -16,12 +16,16 @@ #include "keyple/core/plugin/PluginIOException.hpp" #include "keyple/core/util/cpp/KeypleStd.hpp" #include "keyple/core/util/cpp/StringUtils.hpp" +#include "keyple/core/util/cpp/exception/Exception.hpp" #include "keyple/core/util/cpp/exception/IllegalArgumentException.hpp" #include "keyple/plugin/pcsc/PcscPluginFactoryAdapter.hpp" #include "keyple/plugin/pcsc/PcscReaderAdapter.hpp" +#include "keyple/plugin/pcsc/PcscCardCommunicationProtocol.hpp" #include "keyple/plugin/pcsc/PcscSupportedContactProtocol.hpp" #include "keyple/plugin/pcsc/PcscSupportedContactlessProtocol.hpp" #include "keyple/plugin/pcsc/cpp/CardTerminal.hpp" +#include "keyple/plugin/pcsc/cpp/CardTerminals.hpp" +#include "keyple/plugin/pcsc/cpp/TerminalFactory.hpp" #include "keyple/plugin/pcsc/cpp/exception/CardTerminalException.hpp" namespace keyple { @@ -30,8 +34,11 @@ namespace pcsc { using keyple::core::plugin::PluginIOException; using keyple::core::util::cpp::StringUtils; +using keyple::core::util::cpp::exception::Exception; using keyple::core::util::cpp::exception::IllegalArgumentException; using keyple::plugin::pcsc::cpp::CardTerminal; +using keyple::plugin::pcsc::cpp::CardTerminals; +using keyple::plugin::pcsc::cpp::TerminalFactory; using keyple::plugin::pcsc::cpp::exception::CardTerminalException; std::shared_ptr PcscPluginAdapter::INSTANCE; @@ -41,28 +48,30 @@ const int PcscPluginAdapter::MONITORING_CYCLE_DURATION_MS = 1000; PcscPluginAdapter::PcscPluginAdapter() : PcscPlugin() , ObservablePluginSpi() +, mIsCardTerminalsInitialized(false) { /* Initializes the protocol rules map with default values. */ mProtocolRulesMap = { /* Contactless protocols */ - {PcscSupportedContactlessProtocol::ISO_14443_4.getName(), - PcscSupportedContactlessProtocol::ISO_14443_4.getDefaultRule()}, - {PcscSupportedContactlessProtocol::INNOVATRON_B_PRIME_CARD.getName(), - PcscSupportedContactlessProtocol::INNOVATRON_B_PRIME_CARD - .getDefaultRule()}, - {PcscSupportedContactlessProtocol::MIFARE_ULTRA_LIGHT.getName(), - PcscSupportedContactlessProtocol::MIFARE_ULTRA_LIGHT.getDefaultRule()}, + {PcscCardCommunicationProtocol::ISO_14443_4.getName(), + PcscCardCommunicationProtocol::ISO_14443_4.getDefaultRule()}, + {PcscCardCommunicationProtocol::INNOVATRON_B_PRIME.getName(), + PcscCardCommunicationProtocol::INNOVATRON_B_PRIME.getDefaultRule()}, + {PcscCardCommunicationProtocol::MIFARE_ULTRALIGHT.getName(), + PcscCardCommunicationProtocol::MIFARE_ULTRALIGHT.getDefaultRule()}, {PcscSupportedContactlessProtocol::MIFARE_CLASSIC.getName(), PcscSupportedContactlessProtocol::MIFARE_CLASSIC.getDefaultRule()}, {PcscSupportedContactlessProtocol::MIFARE_DESFIRE.getName(), PcscSupportedContactlessProtocol::MIFARE_DESFIRE.getDefaultRule()}, - {PcscSupportedContactlessProtocol::MEMORY_ST25.getName(), - PcscSupportedContactlessProtocol::MEMORY_ST25.getDefaultRule()}, + {PcscCardCommunicationProtocol::ST25_SRT512.getName(), + PcscCardCommunicationProtocol::ST25_SRT512.getDefaultRule()}, /* Contact protocols */ - {PcscSupportedContactProtocol::ISO_7816_3.getName(), - PcscSupportedContactProtocol::ISO_7816_3.getDefaultRule()}, + {PcscCardCommunicationProtocol::ISO_7816_3.getName(), + PcscCardCommunicationProtocol::ISO_7816_3.getDefaultRule()}, + + /* Legacy protocols for compatibility */ {PcscSupportedContactProtocol::ISO_7816_3_T0.getName(), PcscSupportedContactProtocol::ISO_7816_3_T0.getDefaultRule()}, {PcscSupportedContactProtocol::ISO_7816_3_T1.getName(), @@ -142,18 +151,20 @@ PcscPluginAdapter::onUnregister() const std::vector> PcscPluginAdapter::getCardTerminalList() { - /* Parse the current readers list to create the ReaderSpi(s) associated with - * new reader(s) */ - std::vector> terminals; + /* + * Parse the current readers list to create the ReaderSpi(s) associated with + * new reader(s). + */ try { - const std::vector& terminalNames - = CardTerminal::listTerminals(); - for (const auto& terminalName : terminalNames) { - terminals.push_back(std::make_shared(terminalName)); + if (!mIsCardTerminalsInitialized) { + mTerminals = TerminalFactory::getDefault()->terminals(); + mIsCardTerminalsInitialized = true; } - } catch (const CardTerminalException& e) { + return mTerminals->list(); + + } catch (const Exception& e) { const auto msg = e.getMessage(); if (StringUtils::contains(msg, "SCARD_E_NO_READERS_AVAILABLE")) { @@ -173,11 +184,11 @@ PcscPluginAdapter::getCardTerminalList() } else { throw PluginIOException( "Could not access terminals list", - std::make_shared(e)); + std::make_shared(msg)); } } - return terminals; + return std::vector>(0); } std::shared_ptr diff --git a/src/main/PcscReaderAdapter.cpp b/src/main/PcscReaderAdapter.cpp index 29922ef..86373a7 100644 --- a/src/main/PcscReaderAdapter.cpp +++ b/src/main/PcscReaderAdapter.cpp @@ -13,15 +13,26 @@ #include "keyple/plugin/pcsc/PcscReaderAdapter.hpp" +#if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#include +#endif + #include "keyple/core/plugin/CardIOException.hpp" #include "keyple/core/plugin/ReaderIOException.hpp" #include "keyple/core/plugin/TaskCanceledException.hpp" #include "keyple/core/util/HexUtil.hpp" +#include "keyple/core/util/cpp/Thread.hpp" #include "keyple/core/util/cpp/exception/Exception.hpp" #include "keyple/core/util/cpp/exception/IllegalArgumentException.hpp" #include "keyple/core/util/cpp/exception/IllegalStateException.hpp" +#include "keyple/core/util/cpp/exception/InterruptedException.hpp" #include "keyple/plugin/pcsc/PcscPluginAdapter.hpp" #include "keyple/plugin/pcsc/cpp/exception/CardException.hpp" +#include "keyple/plugin/pcsc/cpp/exception/CardTerminalException.hpp" +#include "keyple/plugin/pcsc/cpp/exception/CardNotPresentException.hpp" namespace keyple { namespace plugin { @@ -31,10 +42,14 @@ using keyple::core::plugin::CardIOException; using keyple::core::plugin::ReaderIOException; using keyple::core::plugin::TaskCanceledException; using keyple::core::util::HexUtil; +using keyple::core::util::cpp::Thread; using keyple::core::util::cpp::exception::Exception; using keyple::core::util::cpp::exception::IllegalArgumentException; using keyple::core::util::cpp::exception::IllegalStateException; +using keyple::core::util::cpp::exception::InterruptedException; using keyple::plugin::pcsc::cpp::exception::CardException; +using keyple::plugin::pcsc::cpp::exception::CardTerminalException; +using keyple::plugin::pcsc::cpp::exception::CardNotPresentException; PcscReaderAdapter::PcscReaderAdapter( std::shared_ptr terminal, @@ -46,11 +61,14 @@ PcscReaderAdapter::PcscReaderAdapter( , mName(terminal->getName()) , mPluginAdapter(pluginAdapter) , mCardMonitoringCycleDuration(cardMonitoringCycleDuration) +, mPingApdu(HexUtil::toByteArray("00C0000000")) // GET RESPONSE , mIsContactless(false) , mProtocol(IsoProtocol::ANY.getValue()) , mIsModeExclusive(false) +, mDisconnectionMode(keyple::plugin::pcsc::PcscReader::DisconnectionMode::RESET) , mLoopWaitCard(false) , mLoopWaitCardRemoval(false) +, mIsObservationActive(false) { #if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__) mIsWindows = true; @@ -92,6 +110,13 @@ PcscReaderAdapter::waitForCardInsertion() mName + ": an error occurred while waitœing for a card insertion.", std::make_shared(e)); } + catch (const CardTerminalException& e) + { + /* Here, it is a communication failure with the reader */ + throw ReaderIOException( + mName + ": an error occurred while waitœing for a card insertion.", + std::make_shared(e)); + } throw TaskCanceledException( mName + ": the wait for a card insertion task has been cancelled."); @@ -156,7 +181,7 @@ PcscReaderAdapter::isCurrentProtocol(const std::string& readerProtocol) const const std::string protocolRule = mPluginAdapter->getProtocolRule(readerProtocol); if (!protocolRule.empty()) { - const std::string atr = HexUtil::toHex(mTerminal->getATR()); + const std::string atr = HexUtil::toHex(mCard->getATR()); isCurrentProtocol = Pattern::compile(protocolRule)->matcher(atr)->matches(); } @@ -173,13 +198,13 @@ PcscReaderAdapter::isCurrentProtocol(const std::string& readerProtocol) const void PcscReaderAdapter::onStartDetection() { - /* Nothing to do here in this reader */ + mIsObservationActive = true; } void PcscReaderAdapter::onStopDetection() { - /* Nothing to do here in this reader */ + mIsObservationActive = false; } const std::string& @@ -191,7 +216,7 @@ PcscReaderAdapter::getName() const void PcscReaderAdapter::openPhysicalChannel() { - if (mIsPhysicalChannelOpen == true) { + if (mCard != nullptr) { return; } @@ -205,9 +230,9 @@ PcscReaderAdapter::openPhysicalChannel() getName(), mProtocol); - mTerminal->openAndConnect(mProtocol); + mCard = mTerminal->connect(mProtocol); if (mIsModeExclusive) { - mTerminal->beginExclusive(); + mCard->beginExclusive(); mLogger->debug( "Reader [%]: open card physical channel in exclusive mode\n", getName()); @@ -218,7 +243,11 @@ PcscReaderAdapter::openPhysicalChannel() getName()); } - mIsPhysicalChannelOpen = true; + mChannel = mCard->getBasicChannel(); + + } catch (const CardNotPresentException& e) { + throw CardIOException( + "Card removed", std::make_shared(e)); } catch (const CardException& e) { throw ReaderIOException( @@ -230,15 +259,29 @@ PcscReaderAdapter::openPhysicalChannel() void PcscReaderAdapter::closePhysicalChannel() { - if (mIsPhysicalChannelOpen == false) { - return; + /* + * If the reader is observed, the actual disconnection will be done in the + * card removal sequence. + */ + if (!mIsObservationActive) { + disconnect(); } +} +void PcscReaderAdapter::disconnect() +{ try { - if (mTerminal != nullptr) { - mTerminal->closeAndDisconnect(mDisconnectionMode); + if (mCard != nullptr) { + /* Disconnect using the extended mode allowing UNPOWER. */ + mCard->disconnect(getDisposition(mDisconnectionMode)); + /* Reset the reader state to avoid bad card detection next time. */ + resetReaderState(); } + } catch (const CardNotPresentException& e) { + throw CardIOException( + "Card removed", std::make_shared(e)); + } catch (const CardException& e) { throw ReaderIOException( "Error while closing physical channel", @@ -248,6 +291,34 @@ PcscReaderAdapter::closePhysicalChannel() resetContext(); } +int PcscReaderAdapter::getDisposition(const DisconnectionMode mode) +{ + switch (mode) { + case DisconnectionMode::RESET: + return SCARD_RESET_CARD; + case DisconnectionMode::LEAVE: + return SCARD_LEAVE_CARD; + case DisconnectionMode::UNPOWER: + return SCARD_UNPOWER_CARD; + case DisconnectionMode::EJECT: + return SCARD_EJECT_CARD; + default: + throw IllegalArgumentException(std::string("Unknown DisconnectionMode: ") + mode); + } +} + +void PcscReaderAdapter::resetReaderState() +{ + try { + if (mDisconnectionMode == DisconnectionMode::UNPOWER) { + mTerminal->connect("*")->disconnect(false); + } + + } catch (const CardException& /*e*/) { + /* NOP */ + } +} + bool PcscReaderAdapter::isPhysicalChannelOpen() const { @@ -258,7 +329,7 @@ bool PcscReaderAdapter::checkCardPresence() { try { - const bool isCardPresent = mTerminal->isCardPresent(false); + const bool isCardPresent = mTerminal->isCardPresent(); closePhysicalChannelSafely(); return isCardPresent; @@ -274,31 +345,24 @@ void PcscReaderAdapter::closePhysicalChannelSafely() { try { - if (mIsPhysicalChannelOpen == true) { - /* - * Force reconnection next time. - * Do not reset the card after disconnecting. - */ - mTerminal->closeAndDisconnect(DisconnectionMode::LEAVE); - } + disconnect(); } catch (const Exception&) { - /* Do nothing */ + /* NOP */ } - - resetContext(); } void PcscReaderAdapter::resetContext() { + mCard = nullptr; mIsPhysicalChannelOpen = false; } const std::string PcscReaderAdapter::getPowerOnData() const { - return HexUtil::toHex(mTerminal->getATR()); + return HexUtil::toHex(mCard->getATR()); } const std::vector @@ -306,26 +370,31 @@ PcscReaderAdapter::transmitApdu(const std::vector& apduCommandData) { std::vector apduResponseData; - if (mIsPhysicalChannelOpen) { + if (mChannel) { try { - apduResponseData = mTerminal->transmitApdu(apduCommandData); + apduResponseData = mChannel->transmit(apduCommandData); + + } catch (const CardNotPresentException& e) { + throw CardIOException( + mName + ": " + e.getMessage(), + std::make_shared(e)); } catch (const CardException& e) { - if (e.getMessage().find("REMOVED") != std::string::npos) { - throw CardIOException( - getName() + ":" + e.getMessage(), - std::make_shared(e)); - } else { - throw ReaderIOException( - getName() + ":" + e.getMessage(), - std::make_shared(e)); - } + throw CardIOException( + getName() + ":" + e.getMessage(), + std::make_shared(e)); } catch (const IllegalStateException& e) { /* Card could have been removed prematurely */ throw CardIOException( getName() + ":" + e.getMessage(), - std::make_shared(e)); + std::make_shared(e.getMessage())); + + } catch (const IllegalArgumentException& e) { + /* Card could have been removed prematurely */ + throw CardIOException( + getName() + ":" + e.getMessage(), + std::make_shared(e.getMessage())); } } else { @@ -372,38 +441,94 @@ PcscReaderAdapter::stopCardPresenceMonitoringDuringProcessing() void PcscReaderAdapter::waitForCardRemoval() { - mLogger->trace( - "Reader [%]: start waiting card removal (loop latency: % ms)\n", - mName, - mCardMonitoringCycleDuration); + mLogger->trace("Reader [%]: start waiting card removal\n", mName); - /* Activate loop */ mLoopWaitCardRemoval = true; + try { + if (mDisconnectionMode == DisconnectionMode::UNPOWER) { + waitForCardRemovalByPolling(); + } else { + waitForCardRemovalStandard(); + } + + } catch (const Exception&) { + } + + /* Finally */ + try { + disconnect(); + + } catch (const Exception& e) { + mLogger->warn( + "Error while disconnecting card during card removal: %\n", + e.getMessage()); + } + + + if (!mLoopWaitCardRemoval) { + mLogger->trace("Reader [%]: waiting card removal stopped\n", mName); + } else { + mLogger->trace("Reader [%]: card removed\n", mName); + } + + if (!mLoopWaitCardRemoval) { + throw TaskCanceledException( + mName + ": the wait for the card removal task has been cancelled."); + } +} + +void +PcscReaderAdapter::waitForCardRemovalByPolling() +{ + try { + while (mLoopWaitCardRemoval) { + transmitApdu(mPingApdu); + Thread::sleep(25); + // if (Thread::isInterrupted()) { + // return; + // } + } + + } catch (const CardIOException& e) { + mLogger->trace( + "Expected IOException while waiting for card removal: %\n", + e.getMessage()); + + } catch (const ReaderIOException& e) { + mLogger->trace( + "Expected IOException while waiting for card removal: %\n", + e.getMessage()); + + } catch (const InterruptedException& e) { + mLogger->trace( + "InterruptedException while waiting for card removal: %\n", + e.getMessage()); + // Thread::currentThread().interrupt(); + } + } + +void PcscReaderAdapter::waitForCardRemovalStandard() +{ try { while (mLoopWaitCardRemoval) { if (mTerminal->waitForCardAbsent(mCardMonitoringCycleDuration)) { - /* Card removed */ - mLogger->trace("Reader [%]: card removed\n", mName); return; } - - // if (Thread.interrupted()) { - // break; + // if (isInterrupted()) { + // return; // } } - mLogger->trace("Reader [%]: waiting card removal stopped\n", mName); - } catch (const CardException& e) { - /* Here, it is a communication failure with the reader */ + mLogger->trace( + "Expected CardException while waiting for card removal: %\n", + e.getMessage()); + throw ReaderIOException( mName + ": an error occurred while waiting for the card removal.", std::make_shared(e)); } - - throw TaskCanceledException( - mName + ": the wait for the card removal task has been cancelled."); } void @@ -420,9 +545,9 @@ PcscReaderAdapter::setSharingMode(const SharingMode sharingMode) if (sharingMode == SharingMode::SHARED) { /* If a card is present, change the mode immediately */ - if (mIsPhysicalChannelOpen) { + if (mCard != nullptr) { try { - mTerminal->endExclusive(); + mCard->endExclusive(); } catch (const CardException& e) { throw IllegalStateException( @@ -484,29 +609,18 @@ const std::vector PcscReaderAdapter::transmitControlCommand( const int commandId, const std::vector& command) { - bool temporaryConnection = false; std::vector response; const int controlCode = mIsWindows ? 0x00310000 | (commandId << 2) : 0x42000000 | commandId; try { - if (mTerminal != nullptr) { - - if (!mTerminal->isConnected()) { - temporaryConnection = true; - mTerminal->openAndConnect( - PcscReader::IsoProtocol::DIRECT.getValue()); - } - - response = mTerminal->transmitControlCommand(controlCode, command); - - if (temporaryConnection) { - mTerminal->closeAndDisconnect(DisconnectionMode::LEAVE); - } + if (mCard != nullptr) { + response = mCard->transmitControlCommand(controlCode, command); } else { - /* C++ don't do virtual cards for now... */ - throw IllegalStateException("Reader failure."); + std::shared_ptr virtualCard = mTerminal->connect("DIRECT"); + response = virtualCard->transmitControlCommand(controlCode, command); + virtualCard->disconnect(false); } } catch (const CardException& e) { diff --git a/src/main/cpp/Card.cpp b/src/main/cpp/Card.cpp new file mode 100644 index 0000000..13a81c0 --- /dev/null +++ b/src/main/cpp/Card.cpp @@ -0,0 +1,115 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#include "keyple/plugin/pcsc/cpp/Card.hpp" + +#include "PcscUtils.hpp" + +#include "keyple/plugin/pcsc/cpp/exception/CardException.hpp" + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { + +using keyple::plugin::pcsc::cpp::exception::CardException; + +Card::Card( + const std::shared_ptr cardTerminal, + const SCARDHANDLE handle, + const std::vector atr, + const DWORD protocol, + const SCARD_IO_REQUEST ioRequest) +: mProtocol(protocol) +, mIORequest(ioRequest) +, mHandle(handle) +, mAtr(atr) +, mCardTerminal(cardTerminal) +{ + +} + +const std::vector& +Card::getATR() const +{ + return mAtr; +} + +void +Card::beginExclusive() +{ + SCardBeginTransaction(mHandle); +} + +void +Card::endExclusive() +{ + SCardEndTransaction(mHandle, SCARD_LEAVE_CARD); +} + +void +Card::disconnect(const bool reset) +{ + SCardDisconnect(mHandle, reset ? SCARD_RESET_CARD : SCARD_LEAVE_CARD); +} + +const std::string +Card::getProtocol() const +{ + switch (mProtocol) { + case SCARD_PROTOCOL_T0: + return "T=0"; + case SCARD_PROTOCOL_T1: + return "T=1"; + default: + return "DIRECT"; + } +} + +std::shared_ptr +Card::getBasicChannel() +{ + return std::make_shared(shared_from_this(), 0); +} + +const std::vector +Card::transmitControlCommand( + const int commandId, const std::vector& command) +{ + char r_apdu[261]; + DWORD dwRecv = sizeof(r_apdu); + + LONG rv = SCardControl( + mHandle, + (DWORD)commandId, + (LPCBYTE)command.data(), + (DWORD)command.size(), + (LPBYTE)r_apdu, + (DWORD)sizeof(r_apdu), + &dwRecv); + if (rv != SCARD_S_SUCCESS) { + mLogger->error( + "SCardControl failed with error: %\n", + std::string(pcsc_stringify_error(rv))); + throw CardException("SCardControl failed"); + } + + std::vector response(r_apdu, r_apdu + dwRecv); + + return response; +} + +} /* namespace cpp */ +} /* namespace pcsc */ +} /* namespace plugin */ +} /* namespace keyple */ \ No newline at end of file diff --git a/src/main/cpp/CardChannel.cpp b/src/main/cpp/CardChannel.cpp new file mode 100644 index 0000000..8f44f6e --- /dev/null +++ b/src/main/cpp/CardChannel.cpp @@ -0,0 +1,157 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#include "keyple/plugin/pcsc/cpp/CardChannel.hpp" + +#include "PcscUtils.hpp" + +#include "keyple/core/util/cpp/KeypleStd.hpp" +#include "keyple/core/util/cpp/exception/IllegalArgumentException.hpp" +#include "keyple/plugin/pcsc/cpp/Card.hpp" +#include "keyple/plugin/pcsc/cpp/exception/CardException.hpp" + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { + +using keyple::core::util::cpp::exception::IllegalArgumentException; +using keyple::plugin::pcsc::cpp::exception::CardException; + +CardChannel::CardChannel(const std::shared_ptr card, const int channel) +: mChannel(channel) +, mIsClosed(true) +, mCard(card) +{ + +} + +std::shared_ptr +CardChannel::getCard() const +{ + return mCard; +} + +std::vector +CardChannel::transmit(const std::vector& apduIn) +{ + if (apduIn.size() == 0) + throw IllegalArgumentException("command cannot be empty"); + + /* Make a copy */ + std::vector _apduIn = apduIn; + + /* To check */ + bool t0GetResponse = true; + bool t1GetResponse = true; + + /* + * Note that we modify the 'command' array in some cases, so it must be + * a copy of the application provided data + */ + int n = static_cast(_apduIn.size()); + bool t0 = mCard->mProtocol == SCARD_PROTOCOL_T0; + bool t1 = mCard->mProtocol == SCARD_PROTOCOL_T1; + + if (t0 && (n >= 7) && (_apduIn[4] == 0)) + throw IllegalArgumentException("Extended len. not supported for T=0"); + + if ((t0 || t1) && (n >= 7)) { + int lc = _apduIn[4] & 0xff; + if (lc != 0) { + if (n == lc + 6) { + n--; + } + } else { + lc = ((_apduIn[5] & 0xff) << 8) | (_apduIn[6] & 0xff); + if (n == lc + 9) { + n -= 2; + } + } + } + + bool getresponse = (t0 && t0GetResponse) || (t1 && t1GetResponse); + int k = 0; + std::vector result; + + while (true) { + if (++k >= 32) { + throw CardException("Could not obtain response"); + } + + char r_apdu[261]; + DWORD dwRecv = sizeof(r_apdu); + uint64_t rv; + + mLogger->debug("transmitApdu - c-apdu >> %\n", _apduIn); + + rv = SCardTransmit( + mCard->mHandle, + &mCard->mIORequest, + (LPCBYTE)_apduIn.data(), + static_cast(_apduIn.size()), + NULL, + (LPBYTE)r_apdu, + &dwRecv); + if (rv != SCARD_S_SUCCESS) { + mLogger->error( + "SCardTransmit failed with error: %\n", + std::string(pcsc_stringify_error(rv))); + throw CardException("ScardTransmit failed"); + } + + std::vector response(r_apdu, r_apdu + dwRecv); + + mLogger->debug("transmitApdu - r-apdu << %\n", response); + + int rn = static_cast(response.size()); + if (getresponse && (rn >= 2)) { + /* See ISO 7816/2005, 5.1.3 */ + if ((rn == 2) && (response[0] == 0x6c)) { + // Resend command using SW2 as short Le field + _apduIn[n - 1] = response[1]; + continue; + } + + if (response[rn - 2] == 0x61) { + /* Issue a GET RESPONSE command with the same CLA using SW2 + * as short Le field */ + if (rn > 2) + result.insert( + result.end(), + response.begin(), + response.begin() + rn - 2); + + std::vector getResponse(5); + getResponse[0] = _apduIn[0]; + getResponse[1] = 0xC0; + getResponse[2] = 0; + getResponse[3] = 0; + getResponse[4] = response[rn - 1]; + n = 5; + _apduIn.swap(getResponse); + continue; + } + } + + result.insert(result.end(), response.begin(), response.begin() + rn); + break; + } + + return result; +} + +} /* namespace cpp */ +} /* namespace pcsc */ +} /* namespace plugin */ +} /* namespace keyple */ \ No newline at end of file diff --git a/src/main/cpp/CardTerminal.cpp b/src/main/cpp/CardTerminal.cpp index 39023d9..64744a7 100644 --- a/src/main/cpp/CardTerminal.cpp +++ b/src/main/cpp/CardTerminal.cpp @@ -15,45 +15,44 @@ #include #include -#include +#include + +#include "PcscUtils.hpp" #include "keyple/core/util/cpp/KeypleStd.hpp" +#include "keyple/core/util/cpp/StringUtils.hpp" #include "keyple/core/util/cpp/System.hpp" #include "keyple/core/util/cpp/Thread.hpp" #include "keyple/core/util/cpp/exception/IllegalArgumentException.hpp" +#include "keyple/core/util/cpp/exception/RuntimeException.hpp" +#include "keyple/plugin/pcsc/cpp/CardTerminals.hpp" +#include "keyple/plugin/pcsc/cpp/exception/CardException.hpp" +#include "keyple/plugin/pcsc/cpp/exception/CardNotPresentException.hpp" #include "keyple/plugin/pcsc/cpp/exception/CardTerminalException.hpp" + namespace keyple { namespace plugin { namespace pcsc { namespace cpp { +using keyple::core::util::cpp::StringUtils; using keyple::core::util::cpp::System; using keyple::core::util::cpp::Thread; using keyple::core::util::cpp::exception::IllegalArgumentException; +using keyple::core::util::cpp::exception::RuntimeException; +using keyple::plugin::pcsc::cpp::exception::CardException; +using keyple::plugin::pcsc::cpp::exception::CardNotPresentException; using keyple::plugin::pcsc::cpp::exception::CardTerminalException; using DisconnectionMode = PcscReader::DisconnectionMode; -#ifdef WIN32 -std::string -pcsc_stringify_error(uint64_t rv) -{ - static char out[20]; - sprintf_s(out, sizeof(out), "0x%08X", static_cast(rv)); - - return std::string(out); -} -#endif - -CardTerminal::CardTerminal(const std::string& name) -: mContext(0) -, mHandle(0) -, mState(0) -, mName(name) -, mContextEstablished(false) +CardTerminal::CardTerminal( + const std::shared_ptr cardTerminals +, const std::string& name) +: mName(name) +, mCardTerminals(cardTerminals) { - memset(&mPioSendPCI, 0, sizeof(SCARD_IO_REQUEST)); } const std::string& @@ -62,375 +61,102 @@ CardTerminal::getName() const return mName; } -const std::vector& -CardTerminal::listTerminals() +std::shared_ptr +CardTerminal::connect(const std::string& protocol) { - ULONG ret; - SCARDCONTEXT context; - char* readers = NULL; - char* ptr = NULL; - DWORD len = 0; - static std::vector list; - - /* Clear list */ - list.clear(); - - ret = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &context); - if (ret != SCARD_S_SUCCESS) { - throw CardTerminalException(pcsc_stringify_error(ret)); - } + int dwPreferredProtocols; + int dwShareMode = SCARD_SHARE_SHARED; - ret = SCardListReaders(context, NULL, NULL, &len); - if (ret != SCARD_S_SUCCESS) { - throw CardTerminalException(pcsc_stringify_error(ret)); - } - - readers = static_cast(calloc(len, sizeof(char))); - - if (readers == NULL) { - /* No readers to add to list */ - return list; - } + std::string _protocol = StringUtils::toupper(protocol); - ret = SCardListReaders(context, NULL, readers, &len); - if (ret != SCARD_S_SUCCESS) { - throw CardTerminalException(pcsc_stringify_error(ret)); + /* Proprietary extension */ + if (StringUtils::startsWith(protocol, "EXCLUSIVE;")) { + dwShareMode = SCARD_SHARE_EXCLUSIVE; + const size_t prefixSize = std::string("EXCLUSIVE;").size(); + _protocol = _protocol.substr(prefixSize, protocol.size() - prefixSize); } - ptr = readers; - - if (!ptr) { - return list; - } - - while (*ptr) { - std::string s(ptr); - list.push_back(s); - ptr += strlen(ptr) + 1; - } - - SCardReleaseContext(context); - free(readers); - - return list; -} - -void -CardTerminal::establishContext() -{ - if (mContextEstablished) - return; - - LONG ret = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &mContext); - if (ret != SCARD_S_SUCCESS) { - mContextEstablished = false; - mLogger->error( - "SCardEstablishContext failed with error: %\n", - std::string(pcsc_stringify_error(ret))); - throw CardTerminalException("SCardEstablishContext failed"); + if ("T=0" == _protocol) { + dwPreferredProtocols = SCARD_PROTOCOL_T0; + } else if ("T=1" == _protocol) { + dwPreferredProtocols = SCARD_PROTOCOL_T1; + } else if ("*" == _protocol) { + dwPreferredProtocols = SCARD_PROTOCOL_ANY; + } else if ("DIRECT" == _protocol) { + /* Connect directly to reader to send control commands. */ + dwPreferredProtocols = 0; + // /* OSX 10.11 would otherwise fail with SCARD_E_INVALID_VALUE + // if (Platform.isMac()) { + // dwPreferredProtocols = SCARD_PROTOCOL_ANY; + // } + dwShareMode = SCARD_SHARE_DIRECT; + } else { + throw IllegalArgumentException( + "Protocol should be one of (prepended with EXCLUSIVE;) T=0, T=1," \ + " *, DIRECT. Got " + protocol); } - mContextEstablished = true; -} - -void -CardTerminal::releaseContext() -{ - if (!mContextEstablished) - return; + DWORD dwProtocol; + SCARDHANDLE handle; + SCARD_IO_REQUEST ioRequest; - SCardReleaseContext(mContext); - mContextEstablished = false; -} - -bool -CardTerminal::connect() -{ LONG rv = SCardConnect( - mContext, + mCardTerminals->mContext, (LPCSTR)mName.c_str(), - SCARD_SHARE_SHARED, - SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, - &mHandle, - &mProtocol); - - return rv == SCARD_S_SUCCESS; -} - -const std::vector -CardTerminal::transmitControlCommand( - const int commandId, const std::vector& command) -{ - char r_apdu[261]; - DWORD dwRecv = sizeof(r_apdu); - - LONG rv = SCardControl( - mHandle, - (DWORD)commandId, - (LPCBYTE)command.data(), - (DWORD)command.size(), - (LPBYTE)r_apdu, - (DWORD)sizeof(r_apdu), - &dwRecv); - if (rv != SCARD_S_SUCCESS) { - mLogger->error( - "SCardControl failed with error: %\n", - std::string(pcsc_stringify_error(rv))); - throw CardTerminalException("SCardControl failed"); - } - - std::vector response(r_apdu, r_apdu + dwRecv); - - return response; -} - -void -CardTerminal::disconnect() -{ - SCardDisconnect(mHandle, SCARD_LEAVE_CARD); - - mHandle = 0; -} - -bool -CardTerminal::isCardPresent(bool release) -{ - (void)release; - try { - establishContext(); - } catch (CardTerminalException& e) { - mLogger->error("isCardPresent - caught CardTerminalException %\n", e); - throw; - } - - bool status = connect(); - disconnect(); - - return status; -} - -bool -CardTerminal::isConnected() -{ - return mContextEstablished && mHandle; -} - -void -CardTerminal::openAndConnect(const std::string& protocol) -{ - LONG rv; - DWORD connectProtocol; - DWORD sharingMode = SCARD_SHARE_SHARED; - BYTE reader[200]; - DWORD readerLen = sizeof(reader); - BYTE _atr[33]; - DWORD atrLen = sizeof(_atr); - - mLogger->debug("[%] openAndConnect - protocol: %\n", mName, protocol); - - try { - establishContext(); - } catch (CardTerminalException& e) { - mLogger->error("openAndConnect - caught CardTerminalException %\n", e); - throw; - } - - if (!protocol.compare(PcscReader::IsoProtocol::ANY.getValue())) { - connectProtocol = SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1; - } else if (!protocol.compare(PcscReader::IsoProtocol::T0.getValue())) { - connectProtocol = SCARD_PROTOCOL_T0; - } else if (!protocol.compare(PcscReader::IsoProtocol::T1.getValue())) { - connectProtocol = SCARD_PROTOCOL_T1; - } else if (!protocol.compare(PcscReader::IsoProtocol::DIRECT.getValue())) { - connectProtocol = 0; - sharingMode = SCARD_SHARE_DIRECT; - } else { - throw IllegalArgumentException("Unsupported protocol " + protocol); - } - - mLogger->debug( - "openAndConnect - connecting to % with protocol: %, " - "connectProtocol: % and sharingMode: %\n", - mName, - protocol, - connectProtocol, - sharingMode); - - rv = SCardConnect( - mContext, - mName.c_str(), - sharingMode, - connectProtocol, - &mHandle, - &mProtocol); - if (rv != SCARD_S_SUCCESS) { - mLogger->error( - "openAndConnect - SCardConnect failed (%)\n", - std::string(pcsc_stringify_error(rv))); - releaseContext(); - throw CardTerminalException("openAndConnect failed"); - } + (DWORD)dwShareMode, + (DWORD)dwPreferredProtocols, + &handle, + &dwProtocol); + + if (rv == SCARD_S_SUCCESS) { + switch (dwProtocol) { + case SCARD_PROTOCOL_T0: + ioRequest = *SCARD_PCI_T0; + break; + case SCARD_PROTOCOL_T1: + ioRequest = *SCARD_PCI_T1; + break; + } - switch (mProtocol) { - case SCARD_PROTOCOL_T0: - mPioSendPCI = *SCARD_PCI_T0; - break; - case SCARD_PROTOCOL_T1: - mPioSendPCI = *SCARD_PCI_T1; - break; - } + DWORD readerLength; + BYTE _atr[33]; + DWORD atrLen = sizeof(_atr); + DWORD state; - rv = SCardStatus( - mHandle, (LPSTR)reader, &readerLen, &mState, &mProtocol, _atr, &atrLen); - if (rv != SCARD_S_SUCCESS) { - mLogger->error( - "openAndConnect - SCardStatus failed (s)\n", - std::string(pcsc_stringify_error(rv))); - releaseContext(); - throw CardTerminalException("openAndConnect failed"); + rv = SCardStatus( + handle, + NULL, + &readerLength, + &state, + &dwProtocol, + _atr, + &atrLen); + + std::vector atr(_atr, _atr + atrLen); + + return std::make_shared( + shared_from_this(), handle, atr, dwProtocol, ioRequest); + } else if (rv == static_cast(SCARD_W_REMOVED_CARD)) { + throw CardNotPresentException("Card not present."); } else { - mLogger->debug("openAndConnect - card state: %\n", mState); + throw RuntimeException("Should not reach here."); } - - mAtr.clear(); - mAtr.insert(mAtr.end(), _atr, _atr + atrLen); -} - -void -CardTerminal::closeAndDisconnect(const DisconnectionMode mode) -{ - mLogger->debug("[%] closeAndDisconnect - mode: %\n", mName, mode); - - SCardDisconnect( - mHandle, - mode == DisconnectionMode::RESET ? SCARD_RESET_CARD : SCARD_LEAVE_CARD); - - releaseContext(); } -const std::vector& -CardTerminal::getATR() -{ - return mAtr; -} - -std::vector -CardTerminal::transmitApdu(const std::vector& apduIn) +bool +CardTerminal::isCardPresent() { - if (apduIn.size() == 0) - throw IllegalArgumentException("command cannot be empty"); - - /* Make a copy */ - std::vector _apduIn = apduIn; - - /* To check */ - bool t0GetResponse = true; - bool t1GetResponse = true; - - /* - * Note that we modify the 'command' array in some cases, so it must be - * a copy of the application provided data - */ - int n = static_cast(_apduIn.size()); - bool t0 = mProtocol == SCARD_PROTOCOL_T0; - bool t1 = mProtocol == SCARD_PROTOCOL_T1; - - if (t0 && (n >= 7) && (_apduIn[4] == 0)) - throw CardTerminalException("Extended len. not supported for T=0"); - - if ((t0 || t1) && (n >= 7)) { - int lc = _apduIn[4] & 0xff; - if (lc != 0) { - if (n == lc + 6) { - n--; - } - } else { - lc = ((_apduIn[5] & 0xff) << 8) | (_apduIn[6] & 0xff); - if (n == lc + 9) { - n -= 2; - } - } - } - - bool getresponse = (t0 && t0GetResponse) || (t1 && t1GetResponse); - int k = 0; - std::vector result; - - while (true) { - if (++k >= 32) { - throw CardTerminalException("Could not obtain response"); - } - - char r_apdu[261]; - DWORD dwRecv = sizeof(r_apdu); - uint64_t rv; - - mLogger->debug("[%] transmitApdu - c-apdu >> %\n", mName, _apduIn); + SCARD_READERSTATE rgReaderStates[1] = { 0 }; + rgReaderStates[0].szReader = mName.c_str(); - rv = SCardTransmit( - mHandle, - &mPioSendPCI, - (LPCBYTE)_apduIn.data(), - static_cast(_apduIn.size()), - NULL, - (LPBYTE)r_apdu, - &dwRecv); - if (rv != SCARD_S_SUCCESS) { - mLogger->error( - "SCardTransmit failed with error: %\n", - std::string(pcsc_stringify_error(rv))); - throw CardTerminalException("ScardTransmit failed"); - } - - std::vector response(r_apdu, r_apdu + dwRecv); - - mLogger->debug("[%] transmitApdu - r-apdu << %\n", mName, response); - - int rn = static_cast(response.size()); - if (getresponse && (rn >= 2)) { - /* See ISO 7816/2005, 5.1.3 */ - if ((rn == 2) && (response[0] == 0x6c)) { - // Resend command using SW2 as short Le field - _apduIn[n - 1] = response[1]; - continue; - } - - if (response[rn - 2] == 0x61) { - /* Issue a GET RESPONSE command with the same CLA using SW2 - * as short Le field */ - if (rn > 2) - result.insert( - result.end(), - response.begin(), - response.begin() + rn - 2); - - std::vector getResponse(5); - getResponse[0] = _apduIn[0]; - getResponse[1] = 0xC0; - getResponse[2] = 0; - getResponse[3] = 0; - getResponse[4] = response[rn - 1]; - n = 5; - _apduIn.swap(getResponse); - continue; - } - } - - result.insert(result.end(), response.begin(), response.begin() + rn); - break; + LONG result + = SCardGetStatusChange(mCardTerminals->mContext, 0, rgReaderStates, 1); + if (result != SCARD_S_SUCCESS) { + throw CardTerminalException( + "Failed to get reader status: error " + std::to_string(result)); } - return result; -} - -void -CardTerminal::beginExclusive() -{ -} - -void -CardTerminal::endExclusive() -{ + return 0 != (rgReaderStates[0].dwEventState & SCARD_STATE_PRESENT); } bool @@ -443,7 +169,7 @@ CardTerminal::waitForCardAbsent(uint64_t timeout) .count(); do { - bool isCardAbsent = !this->isCardPresent(false); + bool isCardAbsent = !this->isCardPresent(); if (isCardAbsent) { return true; } @@ -467,7 +193,7 @@ CardTerminal::waitForCardPresent(uint64_t timeout) .count(); do { - bool isCardPresent = this->isCardPresent(false); + bool isCardPresent = this->isCardPresent(); if (isCardPresent) { return true; } diff --git a/src/main/cpp/CardTerminals.cpp b/src/main/cpp/CardTerminals.cpp new file mode 100644 index 0000000..3a0fbac --- /dev/null +++ b/src/main/cpp/CardTerminals.cpp @@ -0,0 +1,146 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#include "keyple/plugin/pcsc/cpp/CardTerminals.hpp" + +#include +#include +#include + +#include "PcscUtils.hpp" + +#include "keyple/core/util/cpp/exception/IllegalArgumentException.hpp" +#include "keyple/plugin/pcsc/cpp/exception/CardException.hpp" +#include "keyple/plugin/pcsc/cpp/exception/CardTerminalException.hpp" + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { + +using keyple::core::util::cpp::exception::IllegalArgumentException; +using keyple::plugin::pcsc::cpp::exception::CardException; +using keyple::plugin::pcsc::cpp::exception::CardTerminalException; + +CardTerminals::CardTerminals(SCARDCONTEXT& context) +: mContext(context) +{ +} + +void +CardTerminals::waitForChange() +{ + waitForChange(0); +} + +bool +CardTerminals::waitForChange(long timeout) +{ + if (timeout < 0) { + throw IllegalArgumentException( + "Negative timeout " + std::to_string(timeout)); + } else if (timeout == 0) { + timeout = INFINITE; + } + + mZombieReaders.clear(); + + for (auto& reader: mKnownReaders) { + reader.dwCurrentState = reader.dwEventState; + reader.dwEventState = 0; + } + + LONG rv = SCardGetStatusChange( + mContext, timeout, mKnownReaders.data(), static_cast(mKnownReaders.size())); + if (rv == static_cast(SCARD_E_TIMEOUT)) { + return false; + } + + return true; +} + +std::shared_ptr +CardTerminals::getTerminal(const std::string& name) +{ + try { + for (const auto& terminal : list()) { + if (terminal->getName() == name) { + return terminal; + } + } + + return nullptr; + + } catch (const CardException&) { + return nullptr; + } +} + +const std::vector> +CardTerminals::list() +{ + return list(State::ALL); +} + + +const std::vector> +CardTerminals::list(const State /*state*/) +{ + ULONG ret; + char* readers = NULL; + char* ptr = NULL; + DWORD len = 0; + static std::vector> list; + + /* Clear list */ + list.clear(); + + ret = SCardListReaders(mContext, NULL, NULL, &len); + if (ret != SCARD_S_SUCCESS) { + throw CardTerminalException(pcsc_stringify_error(ret)); + } + + readers = static_cast(calloc(len, sizeof(char))); + + if (readers == NULL) { + /* No readers to add to list */ + return list; + } + + ret = SCardListReaders(mContext, NULL, readers, &len); + if (ret != SCARD_S_SUCCESS) { + throw CardTerminalException(pcsc_stringify_error(ret)); + } + + ptr = readers; + + if (!ptr) { + return list; + } + + while (*ptr) { + std::string s(ptr); + auto terminal = std::make_shared(shared_from_this(), s); + list.push_back(terminal); + ptr += strlen(ptr) + 1; + } + + free(readers); + + return list; +} + +} /* namespace cpp */ +} /* namespace pcsc */ +} /* namespace plugin */ +} /* namespace keyple */ \ No newline at end of file diff --git a/src/main/cpp/PcscUtils.hpp b/src/main/cpp/PcscUtils.hpp new file mode 100644 index 0000000..4db0e4c --- /dev/null +++ b/src/main/cpp/PcscUtils.hpp @@ -0,0 +1,52 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#pragma once + +#if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__) + +#include +#include +#include + +#include + +// SCARD_PROTOCOL_ANY is not defined in Windows winscard.h +#ifndef SCARD_PROTOCOL_ANY +#define SCARD_PROTOCOL_ANY (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1) +#endif + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { + +/** + * Windows-specific utility to stringify PC/SC error codes. + * On Linux, pcsc-lite provides pcsc_stringify_error(), but Windows winscard.h + * does not. This function formats the error code as a hexadecimal string. + */ +inline std::string +pcsc_stringify_error(uint64_t rv) +{ + static char out[20]; + sprintf_s(out, sizeof(out), "0x%08X", static_cast(rv)); + return std::string(out); +} + +} // namespace cpp +} // namespace pcsc +} // namespace plugin +} // namespace keyple + +#endif // defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__) diff --git a/src/main/cpp/TerminalFactory.cpp b/src/main/cpp/TerminalFactory.cpp new file mode 100644 index 0000000..9224a61 --- /dev/null +++ b/src/main/cpp/TerminalFactory.cpp @@ -0,0 +1,110 @@ +/****************************************************************************** + * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ * + * * + * See the NOTICE file(s) distributed with this work for additional * + * information regarding copyright ownership. * + * * + * This program and the accompanying materials are made available under the * + * terms of the Eclipse Public License 2.0 which is available at * + * http://www.eclipse.org/legal/epl-2.0 * + * * + * SPDX-License-Identifier: EPL-2.0 * + ******************************************************************************/ + +#include "keyple/plugin/pcsc/cpp/TerminalFactory.hpp" + +#include +#include +#include + +#include "PcscUtils.hpp" + +#include "keyple/plugin/pcsc/cpp/exception/CardTerminalException.hpp" + +namespace keyple { +namespace plugin { +namespace pcsc { +namespace cpp { + +using keyple::plugin::pcsc::cpp::exception::CardTerminalException; + +std::shared_ptr TerminalFactory::mInstance; + +std::shared_ptr +TerminalFactory::getDefault() +{ + if (mInstance == nullptr) { + mInstance = std::shared_ptr(new TerminalFactory()); + + } + + return mInstance; +} + +const std::vector +TerminalFactory::listTerminals() +{ + ULONG ret; + char* readers = NULL; + char* ptr = NULL; + DWORD len = 0; + static std::vector list; + + /* Clear list */ + list.clear(); + + ret = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &mContext); + if (ret != SCARD_S_SUCCESS) { + throw CardTerminalException(pcsc_stringify_error(ret)); + } + + ret = SCardListReaders(mContext, NULL, NULL, &len); + if (ret != SCARD_S_SUCCESS) { + throw CardTerminalException(pcsc_stringify_error(ret)); + } + + readers = static_cast(calloc(len, sizeof(char))); + + if (readers == NULL) { + /* No readers to add to list */ + return list; + } + + ret = SCardListReaders(mContext, NULL, readers, &len); + if (ret != SCARD_S_SUCCESS) { + throw CardTerminalException(pcsc_stringify_error(ret)); + } + + ptr = readers; + + if (!ptr) { + return list; + } + + while (*ptr) { + std::string s(ptr); + list.push_back(s); + ptr += strlen(ptr) + 1; + } + + SCardReleaseContext(mContext); + free(readers); + + return list; +} + +std::shared_ptr +TerminalFactory::terminals() +{ + LONG ret = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &mContext); + if (ret != SCARD_S_SUCCESS) { + throw CardTerminalException(pcsc_stringify_error(ret)); + } + + return std::make_shared(mContext); +} + +} /* namespace cpp */ +} /* namespace pcsc */ +} /* namespace plugin */ +} /* namespace keyple */ \ No newline at end of file