Last active
January 30, 2025 23:03
-
-
Save CodeCouturiers/f9f0a81be3af4cd2b547a06c61f6955d to your computer and use it in GitHub Desktop.
SmartCardReader.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <windows.h> | |
#include <setupapi.h> | |
#include <winscard.h> | |
#include <string> | |
#include <string_view> | |
#include <future> | |
#include <atomic> | |
#include <chrono> | |
#include <thread> | |
#include <memory> | |
#include <system_error> | |
#include <array> | |
#include <vector> | |
#pragma comment(lib, "winscard.lib") | |
/** | |
* @brief Class for handling smart card reader operations | |
* | |
* This class provides an interface for interacting with smart card readers, | |
* including initialization, monitoring, and data reading capabilities. | |
*/ | |
class SmartCardReader { | |
public: | |
SmartCardReader() noexcept : context_(0), cardHandle_(0), isCardPresent_(false), shouldStop_(false) {} | |
// Prevent copying | |
SmartCardReader(const SmartCardReader&) = delete; | |
SmartCardReader& operator=(const SmartCardReader&) = delete; | |
// Allow moving | |
SmartCardReader(SmartCardReader&& other) noexcept | |
: context_(std::exchange(other.context_, 0)) | |
, cardHandle_(std::exchange(other.cardHandle_, 0)) | |
, readerName_(std::move(other.readerName_)) | |
, isCardPresent_(other.isCardPresent_.load()) | |
, shouldStop_(other.shouldStop_.load()) {} | |
SmartCardReader& operator=(SmartCardReader&& other) noexcept { | |
if (this != &other) { | |
cleanup(); | |
context_ = std::exchange(other.context_, 0); | |
cardHandle_ = std::exchange(other.cardHandle_, 0); | |
readerName_ = std::move(other.readerName_); | |
isCardPresent_ = other.isCardPresent_.load(); | |
shouldStop_ = other.shouldStop_.load(); | |
} | |
return *this; | |
} | |
~SmartCardReader() noexcept { | |
cleanup(); | |
} | |
[[nodiscard]] bool initialize() noexcept { | |
LONG result = SCardEstablishContext(SCARD_SCOPE_USER, nullptr, nullptr, &context_); | |
if (result != SCARD_S_SUCCESS) { | |
std::cerr << "Failed to establish context. Error: " | |
<< std::system_category().message(result) << std::endl; | |
return false; | |
} | |
return true; | |
} | |
[[nodiscard]] bool listReaders() noexcept { | |
DWORD readerLen = SCARD_AUTOALLOCATE; | |
LPWSTR readers = nullptr; | |
struct SCardMemoryGuard { | |
SCARDCONTEXT context; | |
LPWSTR ptr; | |
~SCardMemoryGuard() { if (ptr) SCardFreeMemory(context, ptr); } | |
}; | |
LONG result = SCardListReadersW(context_, nullptr, (LPWSTR)&readers, &readerLen); | |
if (result != SCARD_S_SUCCESS || readers == nullptr) { | |
std::cerr << "Failed to list readers. Error: " | |
<< std::system_category().message(result) << std::endl; | |
return false; | |
} | |
SCardMemoryGuard guard{context_, readers}; | |
if (!readers) { | |
std::cerr << "No readers available" << std::endl; | |
return false; | |
} | |
bool foundOurReader = false; | |
int index = 0; | |
for (LPWSTR currentReader = readers; | |
currentReader && *currentReader != L'\0'; | |
currentReader += wcslen(currentReader) + 1) { | |
if (!currentReader) { | |
break; | |
} | |
std::wcout << L"Reader " << index << L": " << currentReader << std::endl; | |
if (!readerName_.empty() && readerName_ == currentReader) { | |
foundOurReader = true; | |
} | |
else if (readerName_.empty()) { | |
readerName_ = currentReader; | |
foundOurReader = true; | |
} | |
++index; | |
} | |
if (!foundOurReader) { | |
readerName_.clear(); | |
return false; | |
} | |
std::wcout << L"Selected reader: " << readerName_ << std::endl; | |
return true; | |
} | |
[[nodiscard]] bool connectToReader() noexcept { | |
if (readerName_.empty()) { | |
std::wcerr << L"Ридер не выбран" << std::endl; | |
return false; | |
} | |
DWORD protocol; | |
LONG result = SCardConnectW(context_, | |
readerName_.c_str(), | |
SCARD_SHARE_SHARED, | |
SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, | |
&cardHandle_, | |
&protocol); | |
if (result != SCARD_S_SUCCESS) { | |
switch (result) { | |
case SCARD_E_NO_SMARTCARD: | |
std::wcerr << L"Карта отсутствует в ридере" << std::endl; | |
break; | |
case SCARD_W_REMOVED_CARD: | |
std::wcerr << L"Карта была извлечена из ридера" << std::endl; | |
break; | |
case SCARD_E_READER_UNAVAILABLE: | |
std::wcerr << L"Ридер недоступен" << std::endl; | |
break; | |
case SCARD_E_SHARING_VIOLATION: | |
std::wcerr << L"Ридер используется другим приложением" << std::endl; | |
break; | |
default: | |
std::wcerr << L"Ошибка подключения к ридеру: " | |
<< std::system_category().message(result).c_str() << std::endl; | |
} | |
return false; | |
} | |
if (!getReaderInfo()) { | |
std::wcerr << L"Failed to get reader info" << std::endl; | |
} | |
return true; | |
} | |
[[nodiscard]] bool readData() noexcept { | |
if (!cardHandle_) { | |
return false; | |
} | |
const BYTE sendBuffer[] = { 0x00, 0xA4, 0x04, 0x00, 0x00 }; | |
std::vector<BYTE> recvBuffer(256); | |
DWORD recvLength = static_cast<DWORD>(recvBuffer.size()); | |
LONG result = SCardTransmit(cardHandle_, | |
SCARD_PCI_T1, | |
sendBuffer, | |
sizeof(sendBuffer), | |
nullptr, | |
recvBuffer.data(), | |
&recvLength); | |
if (result != SCARD_S_SUCCESS) { | |
std::cerr << "Failed to transmit command. Error: " | |
<< std::system_category().message(result) << std::endl; | |
return false; | |
} | |
std::cout << "Received " << recvLength << " bytes:" << std::endl; | |
for (DWORD i = 0; i < recvLength; ++i) { | |
printf("%02X ", recvBuffer[i]); | |
} | |
std::cout << std::endl; | |
return true; | |
} | |
[[nodiscard]] bool getReaderStatus() noexcept { | |
if (readerName_.empty()) { | |
if (initialize() && listReaders()) { | |
std::wcout << L"Found new reader" << std::endl; | |
if (connectToReader()) { | |
if (!getReaderInfo()) { | |
std::wcerr << L"Failed to get reader info" << std::endl; | |
} | |
} | |
} else { | |
std::this_thread::sleep_for(std::chrono::seconds(1)); | |
return true; | |
} | |
} | |
SCARD_READERSTATEW readerState[2] = {}; | |
readerState[0].szReader = readerName_.c_str(); | |
readerState[0].dwCurrentState = SCARD_STATE_UNAWARE; | |
readerState[1].szReader = L"\\\\?PnP?\\Notification"; | |
readerState[1].dwCurrentState = SCARD_STATE_UNAWARE; | |
LONG result = SCardGetStatusChangeW(context_, 100, readerState, 2); | |
if (result != SCARD_S_SUCCESS) { | |
if (result == SCARD_E_TIMEOUT) { | |
return true; | |
} | |
if (result == SCARD_E_NO_READERS_AVAILABLE || | |
result == SCARD_E_UNKNOWN_READER) { | |
cleanup(); | |
readerName_.clear(); | |
isCardPresent_ = false; | |
std::wcout << L"Reader disconnected" << std::endl; | |
return true; | |
} | |
std::cerr << "Failed to get reader status. Error: " | |
<< std::system_category().message(result) << std::endl; | |
return false; | |
} | |
if (readerState[1].dwEventState & SCARD_STATE_CHANGED) { | |
bool initResult = initialize(); | |
bool listResult = false; | |
if (initResult) { | |
listResult = listReaders(); | |
} | |
if (listResult) { | |
std::wcout << L"Reader list updated" << std::endl; | |
if (connectToReader()) { | |
if (!getReaderInfo()) { | |
std::wcerr << L"Failed to get reader info" << std::endl; | |
} | |
} | |
} | |
return true; | |
} | |
if (readerState[0].dwEventState & SCARD_STATE_CHANGED) { | |
if (readerState[0].dwEventState & | |
(SCARD_STATE_EMPTY | SCARD_STATE_UNAVAILABLE)) { | |
cleanup(); | |
readerName_.clear(); | |
isCardPresent_ = false; | |
std::wcout << L"Reader removed" << std::endl; | |
return true; | |
} | |
bool wasCardPresent = isCardPresent_; | |
isCardPresent_ = (readerState[0].dwEventState & SCARD_STATE_PRESENT) != 0; | |
if (wasCardPresent != isCardPresent_) { | |
std::wcout << L"Reader state: " | |
<< (isCardPresent_ ? L"Card present" : L"No card") | |
<< std::endl; | |
if (isCardPresent_) { | |
if (connectToReader()) { | |
if (!getReaderInfo()) { | |
std::wcerr << L"Failed to get reader info" << std::endl; | |
} | |
bool readResult = readData(); | |
if (!readResult) { | |
std::cerr << "Failed to read card data" << std::endl; | |
return false; | |
} | |
} else { | |
std::cerr << "Failed to connect to reader" << std::endl; | |
return false; | |
} | |
} else if (cardHandle_) { | |
SCardDisconnect(cardHandle_, SCARD_LEAVE_CARD); | |
cardHandle_ = 0; | |
} | |
} | |
} | |
return true; | |
} | |
void stopMonitoring() noexcept { | |
shouldStop_ = true; | |
} | |
void startMonitoring() noexcept { | |
shouldStop_ = false; | |
while (!shouldStop_) { | |
bool status = getReaderStatus(); | |
if (!status) { | |
std::cerr << "Error in reader status monitoring" << std::endl; | |
std::this_thread::sleep_for(std::chrono::seconds(1)); | |
if (!initialize()) { | |
std::cerr << "Failed to reinitialize after error" << std::endl; | |
} | |
continue; | |
} | |
std::this_thread::sleep_for(std::chrono::milliseconds(100)); | |
} | |
} | |
bool getReaderInfo() noexcept { | |
if (!cardHandle_) { | |
std::wcerr << L"Not connected to reader" << std::endl; | |
return false; | |
} | |
bool isSerialFound = false; // Add flag to track serial number | |
struct AttrInfo { | |
DWORD attr; | |
const wchar_t* name; | |
const wchar_t* description; | |
bool isHex; | |
}; | |
const AttrInfo attributes[] = { | |
{SCARD_ATTR_VENDOR_NAME, L"Vendor", L"Manufacturer of the reader", false}, | |
{SCARD_ATTR_VENDOR_IFD_TYPE, L"Reader Model", L"Model name and type", false}, | |
{SCARD_ATTR_VENDOR_IFD_VERSION, L"IFD Version", L"Interface Device firmware version", true}, | |
{SCARD_ATTR_VENDOR_IFD_SERIAL_NO, L"Serial Number", L"Reader's unique serial number", false}, | |
{SCARD_ATTR_ATR_STRING, L"Card ATR", L"Answer To Reset - card's initial response", true}, | |
{SCARD_ATTR_ICC_PRESENCE, L"Card Status", L"Whether a card is inserted", true}, | |
{SCARD_ATTR_ICC_INTERFACE_STATUS, L"Interface Status", L"Current interface connection state", true}, | |
{SCARD_ATTR_CURRENT_PROTOCOL_TYPE, L"Current Protocol", L"Communication protocol (T=0 or T=1)", false}, | |
{SCARD_ATTR_ICC_TYPE_PER_ATR, L"Card Type", L"Type of card based on ATR", true} | |
}; | |
std::wcout << L"\nReader Information:" << std::endl; | |
std::wcout << L"===================" << std::endl; | |
for (const auto& attr : attributes) { | |
DWORD attrLen = 0; | |
std::vector<unsigned char> buffer; | |
LONG result = SCardGetAttrib(cardHandle_, attr.attr, nullptr, &attrLen); | |
if (result == SCARD_S_SUCCESS && attrLen > 0) { | |
buffer.resize(attrLen); | |
result = SCardGetAttrib(cardHandle_, attr.attr, buffer.data(), &attrLen); | |
if (result == SCARD_S_SUCCESS) { | |
std::wcout << attr.name << L" (" << attr.description << L"): "; | |
switch (attr.attr) { | |
case SCARD_ATTR_VENDOR_NAME: | |
case SCARD_ATTR_VENDOR_IFD_TYPE: | |
for (size_t i = 0; i < attrLen; ++i) { | |
std::wcout << static_cast<wchar_t>(buffer[i]); | |
} | |
break; | |
case SCARD_ATTR_CURRENT_PROTOCOL_TYPE: | |
if (attrLen >= sizeof(DWORD)) { | |
DWORD protocol = *reinterpret_cast<DWORD*>(buffer.data()); | |
if (protocol == SCARD_PROTOCOL_T0) | |
std::wcout << L"T=0 (byte-oriented protocol)"; | |
else if (protocol == SCARD_PROTOCOL_T1) | |
std::wcout << L"T=1 (block-oriented protocol)"; | |
else | |
std::wcout << L"Unknown protocol"; | |
} | |
break; | |
case SCARD_ATTR_ICC_PRESENCE: | |
std::wcout << (buffer[0] == 0x02 ? | |
L"Smart card is present and properly connected" : | |
L"No smart card in reader"); | |
break; | |
case SCARD_ATTR_ATR_STRING: | |
std::wcout << L"["; | |
for (size_t i = 0; i < attrLen; ++i) { | |
printf("%02X ", buffer[i]); | |
} | |
std::wcout << L"] (Card's power-on response)"; | |
break; | |
case SCARD_ATTR_VENDOR_IFD_SERIAL_NO: | |
{ | |
std::wstring serialNumber; | |
for (size_t i = 0; i < attrLen; ++i) { | |
unsigned char byte = buffer[i]; | |
if (byte >= 32 && byte <= 126) { | |
serialNumber += static_cast<wchar_t>(byte); | |
} else { | |
wchar_t hex[4]; | |
swprintf(hex, 4, L"%02X", byte); | |
if (!serialNumber.empty() && serialNumber.back() != L' ') { | |
serialNumber += L' '; | |
} | |
serialNumber += hex; | |
} | |
} | |
if (!serialNumber.empty()) { | |
isSerialFound = true; | |
} else { | |
std::wcout << L"No serial number available"; | |
} | |
} | |
break; | |
default: | |
if (attr.isHex) { | |
for (size_t i = 0; i < attrLen; ++i) { | |
printf("%02X ", buffer[i]); | |
} | |
} else { | |
if (buffer[attrLen - 1] == 0) { | |
std::wcout << reinterpret_cast<char*>(buffer.data()); | |
} else { | |
for (size_t i = 0; i < attrLen; ++i) { | |
printf("%02X ", buffer[i]); | |
} | |
} | |
} | |
} | |
std::wcout << std::endl; | |
} | |
} | |
} | |
if (!isSerialFound) { | |
std::wcout << L"Warning: No serial number was found for this reader" << std::endl; | |
} | |
return true; | |
} | |
private: | |
void cleanup() noexcept { | |
if (cardHandle_) { | |
SCardDisconnect(cardHandle_, SCARD_LEAVE_CARD); | |
cardHandle_ = 0; | |
} | |
if (context_) { | |
SCardReleaseContext(context_); | |
context_ = 0; | |
} | |
} | |
SCARDCONTEXT context_; | |
SCARDHANDLE cardHandle_; | |
std::wstring readerName_; | |
std::atomic<bool> isCardPresent_; | |
std::atomic<bool> shouldStop_; | |
}; | |
int main() { | |
try { | |
SetConsoleOutputCP(CP_UTF8); | |
setvbuf(stdout, nullptr, _IOFBF, 1000); | |
auto reader = std::make_unique<SmartCardReader>(); | |
std::cout << "Initializing Smart Card subsystem...\n"; | |
if (!reader->initialize()) { | |
std::cerr << "Failed to initialize" << std::endl; | |
return 1; | |
} | |
std::cout << "Looking for readers...\n"; | |
if (!reader->listReaders()) { | |
std::cerr << "No suitable reader found" << std::endl; | |
return 1; | |
} | |
std::cout << "Starting continuous monitoring...\n"; | |
auto monitoringTask = std::async(std::launch::async, | |
&SmartCardReader::startMonitoring, reader.get()); | |
std::cout << "Press Enter to exit...\n"; | |
std::cin.get(); | |
reader->stopMonitoring(); | |
monitoringTask.wait(); | |
return 0; | |
} | |
catch (const std::exception& e) { | |
std::cerr << "Fatal error: " << e.what() << std::endl; | |
return 1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment