Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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
5#include "qpcscslot_p.h"
6#include "qpcsccard_p.h"
7#include <QtCore/QLoggingCategory>
8#include <QtCore/QThread>
9#include <QtCore/QTimer>
10
12
13Q_DECLARE_LOGGING_CATEGORY(QT_NFC_PCSC)
14
15static constexpr auto PollIntervalEnvVar = "QT_NFC_POLL_INTERVAL_MS";
16static constexpr int DefaultPollIntervalMs = 100;
17
18QPcscManager::QPcscManager(QObject *parent) : QObject(parent)
19{
20 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
21
22 int pollInterval = DefaultPollIntervalMs;
23 QByteArray intervalEnv = qgetenv(PollIntervalEnvVar);
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);
112 QPcscSlotName::Ptr list = buf.ptr();
113
114 auto ret = SCardListReaders(m_context, nullptr, list, &listSize);
115#else
116 QPcscSlotName::Ptr list;
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)) {
265 slot->invalidateInsertedCard();
266 slot->deleteLater();
267 }
268 SCardReleaseContext(m_context);
269 m_slots.clear();
270 m_slotStates.clear();
271 }
272}
273
274void QPcscManager::onStartTargetDetectionRequest(QNearFieldTarget::AccessMethod accessMethod)
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
357QT_END_NAMESPACE
void onStopTargetDetectionRequest()
~QPcscManager() override
QPcscCard * connectToCard(QPcscSlot *slot)
static constexpr int DefaultPollIntervalMs
#define LIST_READER_BUFFER_EXTRA