Skip to content

Instantly share code, notes, and snippets.

@CodeCouturiers
Last active January 30, 2025 23:03
Show Gist options
  • Save CodeCouturiers/f9f0a81be3af4cd2b547a06c61f6955d to your computer and use it in GitHub Desktop.
Save CodeCouturiers/f9f0a81be3af4cd2b547a06c61f6955d to your computer and use it in GitHub Desktop.
SmartCardReader.cpp
#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