Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qpcscmanager.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qpcscmanager_p.h"
5#include "qpcscslot_p.h"
6#include "qpcsccard_p.h"
7#include <QtCore/QLoggingCategory>
8#include <QtCore/QThread>
9#include <QtCore/QTimer>
10
12
14
15static constexpr int StateUpdateIntervalMs = 1000;
16
18{
19 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
20
21 m_stateUpdateTimer = new QTimer(this);
22 m_stateUpdateTimer->setInterval(StateUpdateIntervalMs);
23 connect(m_stateUpdateTimer, &QTimer::timeout, this, &QPcscManager::onStateUpdate);
24}
25
27{
28 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
29 if (m_hasContext) {
30 // Destroy all card handles before destroying the PCSC context.
31 for (auto slot : std::as_const(m_slots))
32 slot->invalidateInsertedCard();
33 SCardReleaseContext(m_context);
34 }
35
36 // Stop the worker thread.
37 thread()->quit();
38}
39
40void QPcscManager::processSlotUpdates()
41{
42 for (auto &state : m_slotStates) {
43 auto slot = static_cast<QPcscSlot *>(state.pvUserData);
44 Q_ASSERT(slot != nullptr);
45
46 if ((state.dwEventState & SCARD_STATE_UNKNOWN) != 0)
47 continue;
48
49 if (state.dwEventState == state.dwCurrentState)
50 continue;
51
52 qCDebug(QT_NFC_PCSC) << Qt::hex << state.dwCurrentState << "=>" << state.dwEventState << ":"
53 << slot->name();
54
55 state.dwCurrentState = state.dwEventState;
56 slot->processStateChange(state.dwEventState, m_targetDetectionRunning);
57 }
58}
59
60/*
61 Remove slots that no longer need to be tracked.
62*/
63void QPcscManager::removeSlots()
64{
65 for (auto &state : m_slotStates) {
66 auto slot = static_cast<QPcscSlot *>(state.pvUserData);
67 Q_ASSERT(slot != nullptr);
68
69 // Remove slots that no longer exist, or all slots without cards if
70 // target detection is stopped.
71 if ((state.dwEventState & SCARD_STATE_UNKNOWN) != 0
72 || !(m_targetDetectionRunning || slot->hasCard())) {
73 qCDebug(QT_NFC_PCSC) << "Removing slot:" << slot;
74 state.dwEventState = SCARD_STATE_UNKNOWN;
75 slot->invalidateInsertedCard();
76 m_slots.remove(slot->name());
77 slot->deleteLater();
78 state.pvUserData = nullptr;
79 }
80 }
81
82 // Remove state tracking entries for slots that no longer exist.
83 m_slotStates.removeIf(
84 [](const auto &state) { return (state.dwEventState & SCARD_STATE_UNKNOWN) != 0; });
85}
86
87/*
88 Reads the system slot lists and marks slots that no longer exists, also
89 creates new slot entries if target detection is currently running.
90*/
91void QPcscManager::updateSlotList()
92{
93 Q_ASSERT(m_hasContext);
94
95#ifndef SCARD_AUTOALLOCATE
96 // macOS does not support automatic allocation. Try using a fixed-size
97 // buffer first, extending it if it is not sufficient.
98#define LIST_READER_BUFFER_EXTRA 1024
99 QPcscSlotName buf(nullptr);
100 DWORD listSize = LIST_READER_BUFFER_EXTRA;
101 buf.resize(listSize);
103
104 auto ret = SCardListReaders(m_context, nullptr, list, &listSize);
105#else
107 DWORD listSize = SCARD_AUTOALLOCATE;
108 auto ret = SCardListReaders(m_context, nullptr, reinterpret_cast<QPcscSlotName::Ptr>(&list),
109 &listSize);
110#endif
111
112 if (ret == LONG(SCARD_E_NO_READERS_AVAILABLE)) {
113 list = nullptr;
114 ret = SCARD_S_SUCCESS;
115 }
116#ifndef SCARD_AUTOALLOCATE
117 else if (ret == LONG(SCARD_E_INSUFFICIENT_BUFFER)) {
118 // SCardListReaders() has set listSize to the required size. We add
119 // extra space to reduce possibility of failure if the reader list has
120 // changed since the last call.
121 listSize += LIST_READER_BUFFER_EXTRA;
122 buf.resize(listSize);
123 list = buf.ptr();
124
125 ret = SCardListReaders(m_context, nullptr, list, &listSize);
126 if (ret == LONG(SCARD_E_NO_READERS_AVAILABLE)) {
127 list = nullptr;
128 ret = SCARD_S_SUCCESS;
129 }
130 }
131#undef LIST_READER_BUFFER_EXTRA
132#endif
133
134 if (ret != SCARD_S_SUCCESS) {
135 qCDebug(QT_NFC_PCSC) << "Failed to list readers:" << QPcsc::errorMessage(ret);
136 return;
137 }
138
139#ifdef SCARD_AUTOALLOCATE
140 auto freeList = qScopeGuard([this, list] {
141 if (list) {
142 Q_ASSERT(m_hasContext);
143 SCardFreeMemory(m_context, list);
144 }
145 });
146#endif
147
148 QSet<QPcscSlotName> presentSlots;
149
150 if (list != nullptr) {
151 for (const auto *p = list; *p; p += QPcscSlotName::nameSize(p) + 1)
152 presentSlots.insert(QPcscSlotName(p));
153 }
154
155 // Check current state list and mark slots that are not present anymore to
156 // be removed later.
157 for (auto &state : m_slotStates) {
158 auto slot = static_cast<QPcscSlot *>(state.pvUserData);
159 Q_ASSERT(slot != nullptr);
160
161 if (presentSlots.contains(slot->name()))
162 presentSlots.remove(slot->name());
163 else
164 state.dwEventState = SCARD_STATE_UNKNOWN;
165 }
166
167 if (!m_targetDetectionRunning)
168 return;
169
170 // Add new slots
171 for (auto &&slotName : std::as_const(presentSlots)) {
172 QPcscSlot *slot = new QPcscSlot(slotName, this);
173 qCDebug(QT_NFC_PCSC) << "New slot:" << slot;
174
175 m_slots[slotName] = slot;
176
177 SCARD_READERSTATE state {};
178 state.pvUserData = slot;
179 state.szReader = slot->name().ptr();
180 state.dwCurrentState = SCARD_STATE_UNAWARE;
181
182 m_slotStates.append(state);
183 }
184}
185
186bool QPcscManager::establishContext()
187{
188 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
189
190 Q_ASSERT(!m_hasContext);
191
192 LONG ret = SCardEstablishContext(SCARD_SCOPE_USER, nullptr, nullptr, &m_context);
193 if (ret != SCARD_S_SUCCESS) {
194 qCWarning(QT_NFC_PCSC) << "Failed to establish context:" << QPcsc::errorMessage(ret);
195 return false;
196 }
197 m_hasContext = true;
198
199 return true;
200}
201
202void QPcscManager::onStateUpdate()
203{
204 if (!m_hasContext) {
205 if (!m_targetDetectionRunning) {
206 m_stateUpdateTimer->stop();
207 return;
208 }
209
210 if (!establishContext())
211 return;
212 }
213
214 updateSlotList();
215 removeSlots();
216
217 if (m_slotStates.isEmpty()) {
218 if (!m_targetDetectionRunning) {
219 // Free the context if it is no longer needed to card tracking.
220 SCardReleaseContext(m_context);
221 m_hasContext = false;
222
223 m_stateUpdateTimer->stop();
224 }
225 return;
226 }
227
228 // Both Windows and PCSCLite support interruptable continuos state detection
229 // where SCardCancel() can be used to abort it from another thread.
230 // But that introduces a problem of reliable cancelling of the status change
231 // call and no other. Reliable use of SCardCancel() would probably require
232 // some form of synchronization and polling, and would make the code much
233 // more complicated.
234 // Alternatively, the state detection code could run in a yet another thread
235 // that will not need to be cancelled too often.
236 // For simplicity, the current code just checks for status changes every
237 // second.
238 LONG ret = SCardGetStatusChange(m_context, 0, m_slotStates.data(), m_slotStates.size());
239
240 if (ret == SCARD_S_SUCCESS || ret == LONG(SCARD_E_UNKNOWN_READER)) {
241 processSlotUpdates();
242 removeSlots();
243 } else if (ret == LONG(SCARD_E_CANCELLED) || ret == LONG(SCARD_E_UNKNOWN_READER)
244 || ret == LONG(SCARD_E_TIMEOUT)) {
245 /* ignore */
246 } else {
247 qCWarning(QT_NFC_PCSC) << "SCardGetStatusChange failed:" << QPcsc::errorMessage(ret);
248
249 // Unknown failure. It is likely that the current context will not
250 // recover from it, so destroy it and try to create a new context at the
251 // next iteration.
252 Q_ASSERT(m_hasContext);
253 m_hasContext = false;
254 for (auto slot : std::as_const(m_slots)) {
256 slot->deleteLater();
257 }
258 SCardReleaseContext(m_context);
259 m_slots.clear();
260 m_slotStates.clear();
261 }
262}
263
265{
266 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
267
268 m_requestedMethod = accessMethod;
269
270 if (m_targetDetectionRunning)
271 return;
272
273 m_targetDetectionRunning = true;
274 m_stateUpdateTimer->start();
275}
276
278{
279 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
280 m_targetDetectionRunning = false;
281}
282
284{
285 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
286 Q_ASSERT(slot != nullptr);
287 Q_ASSERT(m_hasContext);
288
289 SCARDHANDLE cardHandle;
290 DWORD activeProtocol;
291
292 LONG ret = SCardConnect(m_context, slot->name().ptr(), SCARD_SHARE_SHARED,
293 SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &cardHandle, &activeProtocol);
294 if (ret != SCARD_S_SUCCESS) {
295 qCDebug(QT_NFC_PCSC) << "Failed to connect to card:" << QPcsc::errorMessage(ret);
296 retryCardDetection(slot);
297 return nullptr;
298 }
299
300 auto card = new QPcscCard(cardHandle, activeProtocol, this);
301 auto uid = card->readUid();
302 auto maxInputLength = card->readMaxInputLength();
303
304 QNearFieldTarget::AccessMethods accessMethods = QNearFieldTarget::TagTypeSpecificAccess;
305 if (card->supportsNdef())
306 accessMethods |= QNearFieldTarget::NdefAccess;
307
308 if (m_requestedMethod != QNearFieldTarget::UnknownAccess
309 && (accessMethods & m_requestedMethod) == 0) {
310 qCDebug(QT_NFC_PCSC) << "Dropping card without required access support";
311 card->deleteLater();
312 return nullptr;
313 }
314
315 if (!card->isValid()) {
316 qCDebug(QT_NFC_PCSC) << "Card became invalid";
317 card->deleteLater();
318
319 retryCardDetection(slot);
320
321 return nullptr;
322 }
323
324 Q_EMIT cardInserted(card, uid, accessMethods, maxInputLength);
325
326 return card;
327}
328
329/*
330 Setup states list so that the card detection for the given slot will
331 be retried on the next iteration.
332
333 This is useful to try to get cards working after reset.
334*/
335void QPcscManager::retryCardDetection(const QPcscSlot *slot)
336{
337 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
338
339 for (auto &state : m_slotStates) {
340 if (state.pvUserData == slot) {
341 state.dwCurrentState = SCARD_STATE_UNAWARE;
342 break;
343 }
344 }
345}
346
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
qsizetype removeIf(Predicate pred)
Definition qlist.h:587
pointer data()
Definition qlist.h:414
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
size_type remove(const Key &key)
Definition qmap.h:299
void clear()
Definition qmap.h:288
AccessMethod
This enum describes the access methods a near field target supports.
\inmodule QtCore
Definition qobject.h:90
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1561
void deleteLater()
\threadsafe
Definition qobject.cpp:2352
void onStartTargetDetectionRequest(QNearFieldTarget::AccessMethod accessMethod)
void onStopTargetDetectionRequest()
~QPcscManager() override
void cardInserted(QPcscCard *card, const QByteArray &uid, QNearFieldTarget::AccessMethods accessMethods, int maxInputLength)
QPcscCard * connectToCard(QPcscSlot *slot)
CPtr ptr() const noexcept
Definition qpcsc_p.h:64
static qsizetype nameSize(CPtr p)
Definition qpcsc.cpp:23
LPSTR Ptr
Definition qpcsc_p.h:60
void invalidateInsertedCard()
Definition qpcscslot.cpp:51
const QPcscSlotName & name() const
Definition qpcscslot_p.h:34
Definition qset.h:18
iterator insert(const T &value)
Definition qset.h:155
void quit()
Definition qthread.cpp:935
\inmodule QtCore
Definition qtimer.h:20
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:208
void stop()
Stops the timer.
Definition qtimer.cpp:226
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
else opt state
[0]
QString errorMessage(LONG error)
Definition qpcsc.cpp:12
Combined button and popup list for selecting options.
QTextStream & hex(QTextStream &stream)
Calls QTextStream::setIntegerBase(16) on stream and returns stream.
#define Q_FUNC_INFO
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
return ret
GLenum GLuint GLenum GLsizei const GLchar * buf
GLfloat GLfloat p
[1]
#define LIST_READER_BUFFER_EXTRA
static QT_BEGIN_NAMESPACE constexpr int StateUpdateIntervalMs
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
#define Q_EMIT
QList< int > list
[14]
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent