Keyple Plugin PC/SC C++ Library - 2.6.3
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/PcscCardCommunicationProtocol.hpp"
33#include "keyple/plugin/pcsc/PcscPluginAdapter.hpp"
34#include "keyple/plugin/pcsc/cpp/CardTerminals.hpp"
35#include "keyple/plugin/pcsc/cpp/TerminalFactory.hpp"
36#include "keyple/plugin/pcsc/cpp/exception/CardException.hpp"
37#include "keyple/plugin/pcsc/cpp/exception/CardNotPresentException.hpp"
38
39namespace keyple {
40namespace plugin {
41namespace pcsc {
42
43using keyple::core::plugin::CardIOException;
44using keyple::core::plugin::ReaderIOException;
45using keyple::core::plugin::TaskCanceledException;
46using keyple::core::util::HexUtil;
47using keyple::core::util::cpp::Thread;
48using keyple::core::util::cpp::exception::Exception;
49using keyple::core::util::cpp::exception::IllegalArgumentException;
50using keyple::core::util::cpp::exception::IllegalStateException;
51using keyple::core::util::cpp::exception::InterruptedException;
52using keyple::plugin::pcsc::cpp::CardTerminals;
53using keyple::plugin::pcsc::cpp::TerminalFactory;
54using keyple::plugin::pcsc::cpp::exception::CardException;
55using keyple::plugin::pcsc::cpp::exception::CardNotPresentException;
56
57PcscReaderAdapter::PcscReaderAdapter(
58 std::shared_ptr<CardTerminal> terminal,
59 std::shared_ptr<PcscPluginAdapter> pluginAdapter,
60 const int cardMonitoringCycleDuration)
61: mIsInitialized(false)
62, mIsPhysicalChannelOpen(false)
63, mCommunicationTerminal(terminal)
64, mName(terminal->getName())
65, mPluginAdapter(pluginAdapter)
66, mCardMonitoringCycleDuration(cardMonitoringCycleDuration)
67, mPingApdu(HexUtil::toByteArray("00C0000000")) // GET RESPONSE
68, mIsContactless(false)
69, mProtocol(IsoProtocol::ANY.getValue())
70, mIsModeExclusive(false)
71, mDisconnectionMode(keyple::plugin::pcsc::PcscReader::DisconnectionMode::RESET)
72, mLoopWaitCard(false)
73, mLoopWaitCardRemoval(false)
74, mIsObservationActive(false)
75{
76#if defined(WIN32) || defined(__MINGW32__) || defined(__MINGW64__)
77 mIsWindows = true;
78#else
79 mIsWindows = false;
80#endif
81
82 /*
83 * Create a separate PC/SC context for monitoring operations to avoid
84 * contention under Linux. This is critical because Linux pcsc-lite does
85 * not handle concurrent access to a single SCARDCONTEXT as robustly as
86 * Windows (see threading differences documentation)
87 */
88 mMonitoringTerminal = createMonitoringTerminal(terminal->getName());
89}
90
91std::shared_ptr<CardTerminal>
92PcscReaderAdapter::createMonitoringTerminal(const std::string& terminalName)
93{
94 try {
95 /* Attempt to create a new TerminalFactory instance to get a separate
96 * PC/SC context */
97 std::shared_ptr<TerminalFactory> monitoringFactory
98 = TerminalFactory::getDefault();
99 std::shared_ptr<CardTerminals> monitoringTerminals
100 = monitoringFactory->terminals();
101
102 /* Find the terminal with the same name in the new context */
103 for (const auto& t : monitoringTerminals->list()) {
104 if (t->getName() == terminalName) {
105 mLogger->debug(
106 "[readerExt=%] Separate monitoring context created to "
107 "improve Linux compatibility\n",
108 terminalName);
109 return t;
110 }
111 }
112
113 /* Terminal not found in new context, fall back to same terminal */
114 mLogger->warn(
115 "[readerExt=%] Could not find terminal in separate context, "
116 "using shared context (may cause issues on Linux)\n",
117 terminalName);
118 return mCommunicationTerminal;
119
120 } catch (const Exception& e) {
121 /* Failed to create separate context, fall back to same terminal */
122 mLogger->warn(
123 "[readerExt=%] Could not create separate monitoring context "
124 "[reason=%], using shared context (may cause issues on Linux)\n",
125 terminalName,
126 e.getMessage());
127 return mCommunicationTerminal;
128 }
129}
130
131void
132PcscReaderAdapter::waitForCardInsertion()
133{
134 mLogger->trace(
135 "[readerExt=%] Starting waiting card insertion [loopLatencyMs=%]\n",
136 getName(),
137 mCardMonitoringCycleDuration);
138
139 /* Activate loop */
140 mLoopWaitCard = true;
141
142 try {
143 while (mLoopWaitCard) {
144 if (mMonitoringTerminal->waitForCardPresent(
145 mCardMonitoringCycleDuration)) {
146 /* Card inserted */
147 mLogger->trace("[readerExt=%] Card inserted\n", getName());
148 return;
149 }
150
151 // if (Thread.interrupted()) {
152 // break;
153 // }
154 }
155
156 mLogger->trace(
157 "[readerExt=%] Waiting card insertion stopped\n", getName());
158
159 } catch (const CardException& e) {
160 /* Here, it is a communication failure with the reader */
161 throw ReaderIOException(
162 "Failed to wait for a card insertion. Reader: " + mName, e);
163 }
164
165 throw TaskCanceledException(
166 "The wait for a card insertion task has been cancelled. Reader: "
167 + mName);
168}
169
170void
171PcscReaderAdapter::stopWaitForCardInsertion()
172{
173 mLoopWaitCard = false;
174}
175
176bool
177PcscReaderAdapter::isProtocolSupported(const std::string& readerProtocol) const
178{
179 /* C++ specific: getProtocolRule throws when protocol not found instead of
180 * returning null */
181 try {
182 const std::string rule
183 = mPluginAdapter->getProtocolRule(readerProtocol);
184
185 return true;
186
187 } catch (const IllegalArgumentException&) {
188 /* Do nothing */
189 }
190
191 return false;
192}
193
194void
195PcscReaderAdapter::activateProtocol(const std::string& readerProtocol)
196{
197 mLogger->trace(
198 "[readerExt=%] Activating protocol takes no action [protocol=%]\n",
199 getName(),
200 readerProtocol);
201}
202
203void
204PcscReaderAdapter::deactivateProtocol(const std::string& readerProtocol)
205{
206 mLogger->trace(
207 "[readerExt=%] de-activating protocol takes no action [protocol=%]\n",
208 getName(),
209 readerProtocol);
210}
211
212bool
213PcscReaderAdapter::isCurrentProtocol(const std::string& readerProtocol) const
214{
215 /*
216 * C++ specific: getProtocolRule throws when protocol not found instead of
217 * returning null.
218 */
219 try {
220 bool isCurrentProtocol = false;
221
222 const std::string protocolRule
223 = mPluginAdapter->getProtocolRule(readerProtocol);
224 if (!protocolRule.empty()) {
225 const std::string atr = HexUtil::toHex(mCard->getATR());
226 isCurrentProtocol
227 = Pattern::compile(protocolRule)->matcher(atr)->matches();
228 }
229
230 return isCurrentProtocol;
231
232 } catch (const IllegalArgumentException&) {
233 /* Do nothing */
234 }
235
236 return false;
237}
238
239void
240PcscReaderAdapter::onStartDetection()
241{
242 mIsObservationActive = true;
243}
244
245void
246PcscReaderAdapter::onStopDetection()
247{
248 mIsObservationActive = false;
249}
250
251const std::string&
252PcscReaderAdapter::getName() const
253{
254 return mName;
255}
256
257void
258PcscReaderAdapter::openPhysicalChannel()
259{
260 if (mCard != nullptr) {
261 return;
262 }
263
264 /*
265 * Init of the card physical channel: if not yet established, opening of a
266 * new physical channel.
267 */
268 try {
269 mLogger->debug(
270 "[readerExt=%] Opening card physical channel [protocol=%]\n",
271 getName(),
272 mProtocol);
273
274 mCard = mCommunicationTerminal->connect(mProtocol);
275 if (mIsModeExclusive) {
276 mCard->beginExclusive();
277 mLogger->debug(
278 "[readerExt=%] Card physical channel opened [mode=EXCLUSIVE]"
279 "\n",
280 getName());
281
282 } else {
283 mLogger->debug(
284 "[readerExt=%] Card physical channel opened [mode=SHARED]\n",
285 getName());
286 }
287
288 mChannel = mCard->getBasicChannel();
289
290 } catch (const CardNotPresentException& e) {
291 throw CardIOException("Card removed. Reader: " + mName, e);
292
293 } catch (const CardException& e) {
294 throw ReaderIOException(
295 "Failed to open the physical channel. Reader: " + mName, e);
296 }
297}
298
299void
300PcscReaderAdapter::closePhysicalChannel()
301{
302 /*
303 * If the reader is observed, the actual disconnection will be done in the
304 * card removal sequence.
305 */
306 if (!mIsObservationActive) {
307 disconnect();
308 }
309}
310
311void PcscReaderAdapter::disconnect()
312{
313 try {
314 if (mCard != nullptr) {
315 const DisconnectionMode effectiveMode
316 = isCurrentProtocol(
317 PcscCardCommunicationProtocol::INNOVATRON_B_PRIME
318 .getName())
319 ? DisconnectionMode::UNPOWER
320 : mDisconnectionMode;
321
322 mCard->disconnect(
323 effectiveMode == DisconnectionMode::UNPOWER
324 || effectiveMode == DisconnectionMode::RESET);
325 /* Reset the reader state to avoid bad card detection next time. */
326 resetReaderState(effectiveMode);
327 }
328
329 } catch (const CardNotPresentException& e) {
330 resetContext();
331 throw CardIOException("Card removed. Reader: " + mName, e);
332
333 } catch (const CardException& e) {
334 resetContext();
335 throw ReaderIOException(
336 "Failed to close the physical channel. Reader: " + mName, e);
337 }
338
339 resetContext();
340}
341
342int PcscReaderAdapter::getDisposition(const DisconnectionMode mode)
343{
344 switch (mode) {
345 case DisconnectionMode::RESET:
346 return SCARD_RESET_CARD;
347 case DisconnectionMode::LEAVE:
348 return SCARD_LEAVE_CARD;
349 case DisconnectionMode::UNPOWER:
350 return SCARD_UNPOWER_CARD;
351 case DisconnectionMode::EJECT:
352 return SCARD_EJECT_CARD;
353 default:
354 throw IllegalArgumentException(std::string("Unknown DisconnectionMode: ") + mode);
355 }
356}
357
358void PcscReaderAdapter::resetReaderState(const DisconnectionMode effectiveMode)
359{
360 try {
361 if (effectiveMode == DisconnectionMode::UNPOWER) {
362 mCommunicationTerminal->connect("*")->disconnect(false);
363 }
364
365 } catch (const CardException& /*e*/) {
366 /* NOP */
367 }
368}
369
370bool
371PcscReaderAdapter::isPhysicalChannelOpen() const
372{
373 return mIsPhysicalChannelOpen;
374}
375
376bool
377PcscReaderAdapter::checkCardPresence()
378{
379 try {
380 const bool isCardPresent = mCommunicationTerminal->isCardPresent();
381 closePhysicalChannelSafely();
382
383 return isCardPresent;
384
385 } catch (const CardException& e) {
386 throw ReaderIOException(
387 "Failed to check card presence. Reader: " + mName, e);
388 }
389}
390
391void
392PcscReaderAdapter::closePhysicalChannelSafely()
393{
394 try {
395 disconnect();
396
397 } catch (const Exception&) {
398 /* NOP */
399 }
400}
401
402void
403PcscReaderAdapter::resetContext()
404{
405 mCard = nullptr;
406 mIsPhysicalChannelOpen = false;
407}
408
409const std::string
410PcscReaderAdapter::getPowerOnData() const
411{
412 return HexUtil::toHex(mCard->getATR());
413}
414
415const std::vector<uint8_t>
416PcscReaderAdapter::transmitApdu(const std::vector<uint8_t>& apduCommandData)
417{
418 std::vector<uint8_t> apduResponseData;
419
420 if (mChannel) {
421 try {
422 apduResponseData = mChannel->transmit(apduCommandData);
423
424 } catch (const CardNotPresentException& e) {
425 throw CardIOException(
426 "Card is not present. Unable to transmit APDU. Reader: "
427 + mName,
428 e);
429
430 } catch (const CardException& e) {
431 if (e.getMessage().find("CARD") != std::string::npos ||
432 e.getMessage().find("NOT_TRANSACTED") != std::string::npos ||
433 e.getMessage().find("INVALID_ATR") != std::string::npos) {
434 throw CardIOException(
435 "Failed to communicate with card. Unable to transmit "
436 "APDU. Reader: "
437 + mName,
438 e);
439
440 } else {
441 throw ReaderIOException(
442 "Failed to communicate with card reader. Unable to "
443 "transmit APDU. Reader: "
444 + mName,
445 e);
446 }
447
448 } catch (const IllegalStateException& e) {
449 /* Card could have been removed prematurely */
450 throw CardIOException(
451 "Card could have been removed prematurely. Unable to "
452 "transmit APDU. Reader: "
453 + mName,
454 e);
455
456 } catch (const IllegalArgumentException& e) {
457 /* Card could have been removed prematurely */
458 throw CardIOException(
459 "Card could have been removed prematurely. Unable to "
460 "transmit APDU. Reader: "
461 + mName,
462 e);
463 }
464
465 } else {
466 /* Could occur if the card was removed */
467 throw CardIOException(
468 "Card channel is null. Unable to transmit APDU. Reader: "
469 + mName);
470 }
471
472 return apduResponseData;
473}
474
475bool
476PcscReaderAdapter::isContactless()
477{
478 if (!mIsInitialized) {
479 /*
480 * First time initialisation, the transmission mode has not yet been
481 * determined or fixed explicitly, let's ask the plugin to determine it
482 * (only once)
483 */
484 mIsContactless = mPluginAdapter->isContactless(getName());
485 }
486
487 return mIsContactless;
488}
489
490void
491PcscReaderAdapter::onUnregister()
492{
493 /* Nothing to do here in this reader */
494}
495
496void
497PcscReaderAdapter::monitorCardPresenceDuringProcessing()
498{
499 doWaitForCardRemoval(
500 !isCurrentProtocol(
501 PcscCardCommunicationProtocol::INNOVATRON_B_PRIME.getName()));
502}
503
504void
505PcscReaderAdapter::stopCardPresenceMonitoringDuringProcessing()
506{
507 stopWaitForCardRemoval();
508}
509
510void
511PcscReaderAdapter::waitForCardRemoval()
512{
513 doWaitForCardRemoval(true);
514}
515
516void
517PcscReaderAdapter::doWaitForCardRemoval(const bool allowPolling)
518{
519 mLogger->trace("[readerExt=%] Starting waiting card removal\n", mName);
520
521 mLoopWaitCardRemoval = true;
522
523 try {
524 if (allowPolling && mDisconnectionMode == DisconnectionMode::UNPOWER) {
525 waitForCardRemovalByPolling();
526 } else {
527 waitForCardRemovalStandard();
528 }
529
530 } catch (const ReaderIOException&) {
531 if (mLoopWaitCardRemoval) {
532 try {
533 disconnect();
534
535 } catch (const Exception& e) {
536 mLogger->warn(
537 "[readerExt=%] Failed to disconnect card during card "
538 "removal sequence [reason=%]\n",
539 mName,
540 e.getMessage());
541 }
542 }
543 throw;
544 }
545
546 /* Finally */
547 if (mLoopWaitCardRemoval) {
548 try {
549 disconnect();
550
551 } catch (const Exception& e) {
552 mLogger->warn(
553 "[readerExt=%] Failed to disconnect card during card removal "
554 "sequence [reason=%]\n",
555 mName,
556 e.getMessage());
557 }
558 }
559
560 if (!mLoopWaitCardRemoval) {
561 mLogger->trace(
562 "[readerExt=%] Waiting card removal stopped\n", mName);
563 } else {
564 mLogger->trace("[readerExt=%] Card removed\n", mName);
565 }
566
567 if (!mLoopWaitCardRemoval) {
568 throw TaskCanceledException(
569 "The wait for the card removal task has been cancelled. "
570 "Reader: "
571 + mName);
572 }
573}
574
575void
576PcscReaderAdapter::waitForCardRemovalByPolling()
577{
578 try {
579 while (mLoopWaitCardRemoval) {
580 transmitApdu(mPingApdu);
581 Thread::sleep(25);
582 // if (Thread::isInterrupted()) {
583 // return;
584 // }
585 }
586
587 } catch (const CardIOException& e) {
588 mLogger->trace(
589 "[readerExt=%] Expected IOException received while waiting for "
590 "card removal [reason=%]\n",
591 getName(),
592 e.getMessage());
593
594 } catch (const ReaderIOException& e) {
595 mLogger->trace(
596 "[readerExt=%] Expected IOException received while waiting for "
597 "card removal [reason=%]\n",
598 getName(),
599 e.getMessage());
600
601 } catch (const InterruptedException& e) {
602 mLogger->trace(
603 "[readerExt=%] InterruptedException received while waiting for "
604 "card removal: %\n",
605 getName(),
606 e.getMessage());
607 // Thread::currentThread().interrupt();
608 }
609 }
610
611void PcscReaderAdapter::waitForCardRemovalStandard()
612{
613 try {
614 while (mLoopWaitCardRemoval) {
615 if (mMonitoringTerminal->waitForCardAbsent(
616 mCardMonitoringCycleDuration)) {
617 return;
618 }
619 // if (isInterrupted()) {
620 // return;
621 // }
622 }
623
624 } catch (const CardException& e) {
625 mLogger->trace(
626 "Expected CardException while waiting for card removal: %\n",
627 e.getMessage());
628
629 throw ReaderIOException(
630 "Failed to wait for the card removal. Reader: " + mName, e);
631 }
632}
633
634void
635PcscReaderAdapter::stopWaitForCardRemoval()
636{
637 mLoopWaitCardRemoval = false;
638}
639
640PcscReader&
641PcscReaderAdapter::setSharingMode(const SharingMode sharingMode)
642{
643 mLogger->info(
644 "[readerExt=%] Set sharing mode [value=%]\n", getName(), sharingMode);
645
646 if (sharingMode == SharingMode::SHARED) {
647 /* If a card is present, change the mode immediately */
648 if (mCard != nullptr) {
649 try {
650 mCard->endExclusive();
651
652 } catch (const CardException& e) {
653 throw IllegalStateException(
654 "Failed to disable exclusive mode. Reader: " + mName, e);
655 }
656 }
657
658 mIsModeExclusive = false;
659
660 } else if (sharingMode == SharingMode::EXCLUSIVE) {
661 mIsModeExclusive = true;
662 }
663
664 return *this;
665}
666
667PcscReader&
668PcscReaderAdapter::setContactless(const bool contactless)
669{
670 mLogger->info(
671 "[readerExt=%] Set contactless type [value=%]\n",
672 getName(),
673 contactless);
674
675 mIsContactless = contactless;
676 mIsInitialized = true;
677
678 return *this;
679}
680
681PcscReader&
682PcscReaderAdapter::setIsoProtocol(const IsoProtocol& isoProtocol)
683{
684 mLogger->info(
685 "[readerExt=%] Set ISO protocol [protocol=%, value=%]\n",
686 getName(),
687 isoProtocol,
688 isoProtocol.getValue());
689
690 mProtocol = isoProtocol.getValue();
691
692 return *this;
693}
694
695PcscReader&
696PcscReaderAdapter::setDisconnectionMode(
697 const DisconnectionMode disconnectionMode)
698{
699 mLogger->info(
700 "[readerExt=%] Set disconnection mode [value=%]\n",
701 getName(),
702 disconnectionMode);
703
704 mDisconnectionMode = disconnectionMode;
705
706 return *this;
707}
708
709const std::vector<uint8_t>
710PcscReaderAdapter::transmitControlCommand(
711 const int commandId, const std::vector<uint8_t>& command)
712{
713 std::vector<uint8_t> response;
714 const int controlCode
715 = mIsWindows ? 0x00310000 | (commandId << 2) : 0x42000000 | commandId;
716
717 try {
718 if (mCard != nullptr) {
719
720 response = mCard->transmitControlCommand(controlCode, command);
721 } else {
722 std::shared_ptr<Card> virtualCard
723 = mCommunicationTerminal->connect("DIRECT");
724 response = virtualCard->transmitControlCommand(controlCode, command);
725 virtualCard->disconnect(false);
726 }
727
728 } catch (const CardException& e) {
729 throw IllegalStateException(
730 "Failed to transmit control command. Reader: " + mName, e);
731 }
732
733 return response;
734}
735
736int
737PcscReaderAdapter::getIoctlCcidEscapeCommandId() const
738{
739 return mIsWindows ? 3500 : 1;
740}
741
742} /* namespace pcsc */
743} /* namespace plugin */
744} /* namespace keyple */
PcscReader::DisconnectionMode DisconnectionMode
Definition: Card.cpp:20