Keyple Plugin PC/SC C++ Library - 2.5.2
Component of the Keyple C++ middleware
PcscReaderAdapter.cpp
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ *
3 * *
4 * See the NOTICE file(s) distributed with this work for additional *
5 * information regarding copyright ownership. *
6 * *
7 * This program and the accompanying materials are made available under the *
8 * terms of the Eclipse Public License 2.0 which is available at *
9 * http://www.eclipse.org/legal/epl-2.0 *
10 * *
11 * SPDX-License-Identifier: EPL-2.0 *
12 ******************************************************************************/
13
14#include "keyple/plugin/pcsc/PcscReaderAdapter.hpp"
15
16#if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__)
17#include <winscard.h>
18#else
19#include <PCSC/wintypes.h>
20#include <PCSC/winscard.h>
21#endif
22
23#include "keyple/core/plugin/CardIOException.hpp"
24#include "keyple/core/plugin/ReaderIOException.hpp"
25#include "keyple/core/plugin/TaskCanceledException.hpp"
26#include "keyple/core/util/HexUtil.hpp"
27#include "keyple/core/util/cpp/Thread.hpp"
28#include "keyple/core/util/cpp/exception/Exception.hpp"
29#include "keyple/core/util/cpp/exception/IllegalArgumentException.hpp"
30#include "keyple/core/util/cpp/exception/IllegalStateException.hpp"
31#include "keyple/core/util/cpp/exception/InterruptedException.hpp"
32#include "keyple/plugin/pcsc/PcscPluginAdapter.hpp"
33#include "keyple/plugin/pcsc/cpp/exception/CardException.hpp"
34#include "keyple/plugin/pcsc/cpp/exception/CardNotPresentException.hpp"
35
36namespace keyple {
37namespace plugin {
38namespace pcsc {
39
40using keyple::core::plugin::CardIOException;
41using keyple::core::plugin::ReaderIOException;
42using keyple::core::plugin::TaskCanceledException;
43using keyple::core::util::HexUtil;
44using keyple::core::util::cpp::Thread;
45using keyple::core::util::cpp::exception::Exception;
46using keyple::core::util::cpp::exception::IllegalArgumentException;
47using keyple::core::util::cpp::exception::IllegalStateException;
48using keyple::core::util::cpp::exception::InterruptedException;
49using keyple::plugin::pcsc::cpp::exception::CardException;
50using keyple::plugin::pcsc::cpp::exception::CardNotPresentException;
51
52PcscReaderAdapter::PcscReaderAdapter(
53 std::shared_ptr<CardTerminal> terminal,
54 std::shared_ptr<PcscPluginAdapter> pluginAdapter,
55 const int cardMonitoringCycleDuration)
56: mIsInitialized(false)
57, mIsPhysicalChannelOpen(false)
58, mTerminal(terminal)
59, mName(terminal->getName())
60, mPluginAdapter(pluginAdapter)
61, mCardMonitoringCycleDuration(cardMonitoringCycleDuration)
62, mPingApdu(HexUtil::toByteArray("00C0000000")) // GET RESPONSE
63, mIsContactless(false)
64, mProtocol(IsoProtocol::ANY.getValue())
65, mIsModeExclusive(false)
66, mDisconnectionMode(keyple::plugin::pcsc::PcscReader::DisconnectionMode::RESET)
67, mLoopWaitCard(false)
68, mLoopWaitCardRemoval(false)
69, mIsObservationActive(false)
70{
71#if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__)
72 mIsWindows = true;
73#else
74 mIsWindows = false;
75#endif
76}
77
78void
79PcscReaderAdapter::waitForCardInsertion()
80{
81 mLogger->trace(
82 "Reader [%]: start waiting card insertion (loop latency: % ms)\n",
83 getName(),
84 mCardMonitoringCycleDuration);
85
86 /* Activate loop */
87 mLoopWaitCard = true;
88
89 try {
90 while (mLoopWaitCard) {
91 if (mTerminal->waitForCardPresent(mCardMonitoringCycleDuration)) {
92 /* Card inserted */
93 mLogger->trace("Reader [%]: card inserted\n", getName());
94 return;
95 }
96
97 // if (Thread.interrupted()) {
98 // break;
99 // }
100 }
101
102 mLogger->trace(
103 "Reader [%]: waiting card insertion stopped\n", getName());
104
105 } catch (const CardException& e) {
106 /* Here, it is a communication failure with the reader */
107 throw ReaderIOException(
108 mName + ": an error occurred while waiting for a card insertion.",
109 e);
110 }
111
112 throw TaskCanceledException(
113 mName + ": the wait for a card insertion task has been cancelled.");
114}
115
116void
117PcscReaderAdapter::stopWaitForCardInsertion()
118{
119 mLoopWaitCard = false;
120}
121
122bool
123PcscReaderAdapter::isProtocolSupported(const std::string& readerProtocol) const
124{
125 /* C++ specific: getProtocolRule throws when protocol not found instead of
126 * returning null */
127 try {
128 const std::string rulmIsPhysicalChannelOpen
129 = mPluginAdapter->getProtocolRule(readerProtocol);
130
131 return true;
132
133 } catch (const IllegalArgumentException&) {
134 /* Do nothing */
135 }
136
137 return false;
138}
139
140void
141PcscReaderAdapter::activateProtocol(const std::string& readerProtocol)
142{
143 mLogger->trace(
144 "%: stop waiting for card insertion requested.\n", getName());
145 mLogger->trace(
146 "Reader [%]: activating protocol [%] takes no action\n",
147 getName(),
148 readerProtocol);
149
150 mLoopWaitCard = false;
151}
152
153void
154PcscReaderAdapter::deactivateProtocol(const std::string& readerProtocol)
155{
156 mLogger->trace(
157 "Reader [%]: de-activating protocol [%] takes no action\n",
158 getName(),
159 readerProtocol);
160}
161
162bool
163PcscReaderAdapter::isCurrentProtocol(const std::string& readerProtocol) const
164{
165 /*
166 * C++ specific: getProtocolRule throws when protocol not found instead of
167 * returning null.
168 */
169 try {
170 bool isCurrentProtocol = false;
171
172 const std::string protocolRule
173 = mPluginAdapter->getProtocolRule(readerProtocol);
174 if (!protocolRule.empty()) {
175 const std::string atr = HexUtil::toHex(mCard->getATR());
176 isCurrentProtocol
177 = Pattern::compile(protocolRule)->matcher(atr)->matches();
178 }
179
180 return isCurrentProtocol;
181
182 } catch (const IllegalArgumentException&) {
183 /* Do nothing */
184 }
185
186 return false;
187}
188
189void
190PcscReaderAdapter::onStartDetection()
191{
192 mIsObservationActive = true;
193}
194
195void
196PcscReaderAdapter::onStopDetection()
197{
198 mIsObservationActive = false;
199}
200
201const std::string&
202PcscReaderAdapter::getName() const
203{
204 return mName;
205}
206
207void
208PcscReaderAdapter::openPhysicalChannel()
209{
210 if (mCard != nullptr) {
211 return;
212 }
213
214 /*
215 * Init of the card physical channel: if not yet established, opening of a
216 * new physical channel.
217 */
218 try {
219 mLogger->debug(
220 "Reader [%]: open card physical channel for protocol [%]\n",
221 getName(),
222 mProtocol);
223
224 mCard = mTerminal->connect(mProtocol);
225 if (mIsModeExclusive) {
226 mCard->beginExclusive();
227 mLogger->debug(
228 "Reader [%]: open card physical channel in exclusive mode\n",
229 getName());
230
231 } else {
232 mLogger->debug(
233 "%: opening of a card physical channel in shared mode\n",
234 getName());
235 }
236
237 mChannel = mCard->getBasicChannel();
238
239 } catch (const CardNotPresentException& e) {
240 throw CardIOException("Card removed", e);
241
242 } catch (const CardException& e) {
243 throw ReaderIOException(
244 getName() + ": Error while opening Physical Channel", e);
245 }
246}
247
248void
249PcscReaderAdapter::closePhysicalChannel()
250{
251 /*
252 * If the reader is observed, the actual disconnection will be done in the
253 * card removal sequence.
254 */
255 if (!mIsObservationActive) {
256 disconnect();
257 }
258}
259
260void PcscReaderAdapter::disconnect()
261{
262 try {
263 if (mCard != nullptr) {
264 /* Disconnect using the extended mode allowing UNPOWER. */
265 mCard->disconnect(getDisposition(mDisconnectionMode));
266 /* Reset the reader state to avoid bad card detection next time. */
267 resetReaderState();
268 }
269
270 } catch (const CardNotPresentException& e) {
271 throw CardIOException("Card removed", e);
272
273 } catch (const CardException& e) {
274 throw ReaderIOException("Error while closing physical channel", e);
275 }
276
277 resetContext();
278}
279
280int PcscReaderAdapter::getDisposition(const DisconnectionMode mode)
281{
282 switch (mode) {
283 case DisconnectionMode::RESET:
284 return SCARD_RESET_CARD;
285 case DisconnectionMode::LEAVE:
286 return SCARD_LEAVE_CARD;
287 case DisconnectionMode::UNPOWER:
288 return SCARD_UNPOWER_CARD;
289 case DisconnectionMode::EJECT:
290 return SCARD_EJECT_CARD;
291 default:
292 throw IllegalArgumentException(std::string("Unknown DisconnectionMode: ") + mode);
293 }
294}
295
296void PcscReaderAdapter::resetReaderState()
297{
298 try {
299 if (mDisconnectionMode == DisconnectionMode::UNPOWER) {
300 mTerminal->connect("*")->disconnect(false);
301 }
302
303 } catch (const CardException& /*e*/) {
304 /* NOP */
305 }
306}
307
308bool
309PcscReaderAdapter::isPhysicalChannelOpen() const
310{
311 return mIsPhysicalChannelOpen;
312}
313
314bool
315PcscReaderAdapter::checkCardPresence()
316{
317 try {
318 const bool isCardPresent = mTerminal->isCardPresent();
319 closePhysicalChannelSafely();
320
321 return isCardPresent;
322
323 } catch (const CardException& e) {
324 throw ReaderIOException("Exception occurred in isCardPresent", e);
325 }
326}
327
328void
329PcscReaderAdapter::closePhysicalChannelSafely()
330{
331 try {
332 disconnect();
333
334 } catch (const Exception&) {
335 /* NOP */
336 }
337}
338
339void
340PcscReaderAdapter::resetContext()
341{
342 mCard = nullptr;
343 mIsPhysicalChannelOpen = false;
344}
345
346const std::string
347PcscReaderAdapter::getPowerOnData() const
348{
349 return HexUtil::toHex(mCard->getATR());
350}
351
352const std::vector<uint8_t>
353PcscReaderAdapter::transmitApdu(const std::vector<uint8_t>& apduCommandData)
354{
355 std::vector<uint8_t> apduResponseData;
356
357 if (mChannel) {
358 try {
359 apduResponseData = mChannel->transmit(apduCommandData);
360
361 } catch (const CardNotPresentException& e) {
362 throw CardIOException(mName + ": " + e.getMessage(), e);
363
364 } catch (const CardException& e) {
365 if (e.getMessage().find("CARD") != std::string::npos ||
366 e.getMessage().find("NOT_TRANSACTED") != std::string::npos ||
367 e.getMessage().find("INVALID_ATR") != std::string::npos) {
368 throw CardIOException(getName() + ":" + e.getMessage(), e);
369
370 } else {
371 throw ReaderIOException(getName() + ":" + e.getMessage(), e);
372 }
373
374 } catch (const IllegalStateException& e) {
375 /* Card could have been removed prematurely */
376 throw CardIOException(
377 getName() + ":" + e.getMessage(), e);
378
379 } catch (const IllegalArgumentException& e) {
380 /* Card could have been removed prematurely */
381 throw CardIOException(
382 getName() + ":" + e.getMessage(), e);
383 }
384
385 } else {
386 /* Could occur if the card was removed */
387 throw CardIOException(getName() + ": null channel.");
388 }
389
390 return apduResponseData;
391}
392
393bool
394PcscReaderAdapter::isContactless()
395{
396 if (!mIsInitialized) {
397 /*
398 * First time initialisation, the transmission mode has not yet been
399 * determined or fixed explicitly, let's ask the plugin to determine it
400 * (only once)
401 */
402 mIsContactless = mPluginAdapter->isContactless(getName());
403 }
404
405 return mIsContactless;
406}
407
408void
409PcscReaderAdapter::onUnregister()
410{
411 /* Nothing to do here in this reader */
412}
413
414void
415PcscReaderAdapter::monitorCardPresenceDuringProcessing()
416{
417 waitForCardRemoval();
418}
419
420void
421PcscReaderAdapter::stopCardPresenceMonitoringDuringProcessing()
422{
423 stopWaitForCardRemoval();
424}
425
426void
427PcscReaderAdapter::waitForCardRemoval()
428{
429 mLogger->trace("Reader [%]: start waiting card removal\n", mName);
430
431 mLoopWaitCardRemoval = true;
432
433 try {
434 if (mDisconnectionMode == DisconnectionMode::UNPOWER) {
435 waitForCardRemovalByPolling();
436 } else {
437 waitForCardRemovalStandard();
438 }
439
440 } catch (const Exception&) {
441 }
442
443 /* Finally */
444 try {
445 disconnect();
446
447 } catch (const Exception& e) {
448 mLogger->warn(
449 "Error while disconnecting card during card removal: %\n",
450 e.getMessage());
451 }
452
453
454 if (!mLoopWaitCardRemoval) {
455 mLogger->trace("Reader [%]: waiting card removal stopped\n", mName);
456 } else {
457 mLogger->trace("Reader [%]: card removed\n", mName);
458 }
459
460 if (!mLoopWaitCardRemoval) {
461 throw TaskCanceledException(
462 mName + ": the wait for the card removal task has been cancelled.");
463 }
464}
465
466void
467PcscReaderAdapter::waitForCardRemovalByPolling()
468{
469 try {
470 while (mLoopWaitCardRemoval) {
471 transmitApdu(mPingApdu);
472 Thread::sleep(25);
473 // if (Thread::isInterrupted()) {
474 // return;
475 // }
476 }
477
478 } catch (const CardIOException& e) {
479 mLogger->trace(
480 "Expected IOException while waiting for card removal: %\n",
481 e.getMessage());
482
483 } catch (const ReaderIOException& e) {
484 mLogger->trace(
485 "Expected IOException while waiting for card removal: %\n",
486 e.getMessage());
487
488 } catch (const InterruptedException& e) {
489 mLogger->trace(
490 "InterruptedException while waiting for card removal: %\n",
491 e.getMessage());
492 // Thread::currentThread().interrupt();
493 }
494 }
495
496void PcscReaderAdapter::waitForCardRemovalStandard()
497{
498 try {
499 while (mLoopWaitCardRemoval) {
500 if (mTerminal->waitForCardAbsent(mCardMonitoringCycleDuration)) {
501 return;
502 }
503 // if (isInterrupted()) {
504 // return;
505 // }
506 }
507
508 } catch (const CardException& e) {
509 mLogger->trace(
510 "Expected CardException while waiting for card removal: %\n",
511 e.getMessage());
512
513 throw ReaderIOException(
514 mName + ": an error occurred while waiting for the card removal.",
515 e);
516 }
517}
518
519void
520PcscReaderAdapter::stopWaitForCardRemoval()
521{
522 mLoopWaitCardRemoval = false;
523}
524
525PcscReader&
526PcscReaderAdapter::setSharingMode(const SharingMode sharingMode)
527{
528 mLogger->trace(
529 "Reader [%]: set sharing mode to [%]\n", getName(), sharingMode);
530
531 if (sharingMode == SharingMode::SHARED) {
532 /* If a card is present, change the mode immediately */
533 if (mCard != nullptr) {
534 try {
535 mCard->endExclusive();
536
537 } catch (const CardException& e) {
538 throw IllegalStateException(
539 "Couldn't disable exclusive mode", e);
540 }
541 }
542
543 mIsModeExclusive = false;
544
545 } else if (sharingMode == SharingMode::EXCLUSIVE) {
546 mIsModeExclusive = true;
547 }
548
549 return *this;
550}
551
552PcscReader&
553PcscReaderAdapter::setContactless(const bool contactless)
554{
555 mLogger->trace(
556 "Reader [%]: set contactless type to [%]\n", getName(), contactless);
557
558 mIsContactless = contactless;
559 mIsInitialized = true;
560
561 return *this;
562}
563
564PcscReader&
565PcscReaderAdapter::setIsoProtocol(const IsoProtocol& isoProtocol)
566{
567 mLogger->trace(
568 "Reader [%]: set ISO protocol to [%] (%)\n",
569 getName(),
570 isoProtocol,
571 isoProtocol.getValue());
572
573 mProtocol = isoProtocol.getValue();
574
575 return *this;
576}
577
578PcscReader&
579PcscReaderAdapter::setDisconnectionMode(
580 const DisconnectionMode disconnectionMode)
581{
582 mLogger->trace(
583 "Reader [%]: set disconnection mode to [%]\n",
584 getName(),
585 disconnectionMode);
586
587 mDisconnectionMode = disconnectionMode;
588
589 return *this;
590}
591
592const std::vector<uint8_t>
593PcscReaderAdapter::transmitControlCommand(
594 const int commandId, const std::vector<uint8_t>& command)
595{
596 std::vector<uint8_t> response;
597 const int controlCode
598 = mIsWindows ? 0x00310000 | (commandId << 2) : 0x42000000 | commandId;
599
600 try {
601 if (mCard != nullptr) {
602
603 response = mCard->transmitControlCommand(controlCode, command);
604 } else {
605 std::shared_ptr<Card> virtualCard = mTerminal->connect("DIRECT");
606 response = virtualCard->transmitControlCommand(controlCode, command);
607 virtualCard->disconnect(false);
608 }
609
610 } catch (const CardException& e) {
611 throw IllegalStateException("Reader failure.", e);
612 }
613
614 return response;
615}
616
617int
618PcscReaderAdapter::getIoctlCcidEscapeCommandId() const
619{
620 return mIsWindows ? 3500 : 1;
621}
622
623} /* namespace pcsc */
624} /* namespace plugin */
625} /* namespace keyple */
PcscReader::DisconnectionMode DisconnectionMode
Definition: Card.cpp:20