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