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
qpcsccard.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// Qt-Security score:critical reason:data-parser
4
5#include "qpcsccard_p.h"
6#include "ndef/qnfctagtype4ndeffsm_p.h"
7#include "qapduutils_p.h"
8#include <QtCore/QLoggingCategory>
9#include <QtCore/QTimer>
10
11#if defined(Q_OS_DARWIN)
12# define SCARD_ATTR_MAXINPUT 0x0007A007
13#elif !defined(Q_OS_WIN)
14# include <reader.h>
15#endif
16
18
19Q_DECLARE_LOGGING_CATEGORY(QT_NFC_PCSC)
20
21/*
22 Windows SCardBeginTransaction() documentation says the following:
23
24 If a transaction is held on the card for more than five seconds with no
25 operations happening on that card, then the card is reset. Calling any
26 of the Smart Card and Reader Access Functions or Direct Card Access
27 Functions on the card that is transacted results in the timer being
28 reset to continue allowing the transaction to be used.
29
30 We use a timer to ensure that transactions do not timeout unexpectedly.
31*/
32static constexpr int KeepAliveIntervalMs = 2500;
33
34/*
35 Start a temporary transaction if a persistent transaction was not already
36 started due to call to onSendCommandRequest().
37
38 If a temporary transaction was initiated, it is ended when this object is
39 destroyed.
40*/
41QPcscCard::Transaction::Transaction(QPcscCard *card) : m_card(card)
42{
43 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
44 if (!m_card->isValid() || m_card->m_inAutoTransaction)
45 return;
46
47 auto ret = SCardBeginTransaction(m_card->m_handle);
48 if (ret != SCARD_S_SUCCESS) {
49 qCWarning(QT_NFC_PCSC) << "SCardBeginTransaction failed:" << QPcsc::errorMessage(ret);
50 m_card->invalidate();
51 return;
52 }
53
54 m_initiated = true;
55}
56
57QPcscCard::Transaction::~Transaction()
58{
59 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
60 if (!m_initiated || !m_card->isValid())
61 return;
62
63 auto ret = SCardEndTransaction(m_card->m_handle, SCARD_LEAVE_CARD);
64
65 if (ret != SCARD_S_SUCCESS) {
66 qCWarning(QT_NFC_PCSC) << "SCardEndTransaction failed:" << QPcsc::errorMessage(ret);
67 m_card->invalidate();
68 }
69}
70
71QPcscCard::QPcscCard(SCARDHANDLE handle, DWORD protocol, QObject *parent)
72 : QObject(parent), m_handle(handle)
73{
74 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
75
76 m_keepAliveTimer = new QTimer(this);
77 m_keepAliveTimer->setInterval(KeepAliveIntervalMs);
78 connect(m_keepAliveTimer, &QTimer::timeout, this, &QPcscCard::onKeepAliveTimeout);
79
80 m_ioPci.dwProtocol = protocol;
81 m_ioPci.cbPciLength = sizeof(m_ioPci);
82
83 // Assume that everything is NFC Tag Type 4 for now
84 m_tagDetectionFsm = std::make_unique<QNfcTagType4NdefFsm>();
85
86 performNdefDetection();
87}
88
90{
91 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
93}
94
95void QPcscCard::performNdefDetection()
96{
97 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
98
99 if (!m_isValid)
100 return;
101
102 Transaction transaction(this);
103
104 auto action = m_tagDetectionFsm->detectNdefSupport();
105
106 while (action == QNdefAccessFsm::SendCommand) {
107 auto command = m_tagDetectionFsm->getCommand(action);
108
109 if (action == QNdefAccessFsm::ProvideResponse) {
110 auto result = sendCommand(command, NoAutoTransaction);
111 action = m_tagDetectionFsm->provideResponse(result.response);
112 }
113 }
114
115 qCDebug(QT_NFC_PCSC) << "NDEF detection result" << action;
116
117 m_supportsNdef = action == QNdefAccessFsm::Done;
118 qCDebug(QT_NFC_PCSC) << "NDEF supported:" << m_supportsNdef;
119}
120
121/*
122 Release the resource associated with the card and notify the NFC target
123 about disconnection. The card is deleted when automatic deletion is enabled.
124*/
126{
127 if (!m_isValid)
128 return;
129
130 SCardDisconnect(m_handle, m_inAutoTransaction ? SCARD_RESET_CARD : SCARD_LEAVE_CARD);
131
132 m_isValid = false;
133 m_inAutoTransaction = false;
134
135 Q_EMIT disconnected();
136 Q_EMIT invalidated();
137
138 if (m_autodelete)
139 deleteLater();
140}
141
142/*
143 Send the given command to the card.
144
145 Start an automatic transaction if autoTransaction is StartAutoTransaction
146 and it is not already started. This automatic transaction lasts until the
147 invalidate() or onDisconnectRequest() is called.
148
149 The automatic transaction is used to ensure that other applications do not
150 interfere with commands sent by the user application.
151
152 If autoTransaction is NoAutoTransaction, then the calling code should ensure
153 that either the command is atomic or that a temporary transaction is started
154 using Transaction object.
155*/
156QPcsc::RawCommandResult QPcscCard::sendCommand(const QByteArray &command,
157 QPcscCard::AutoTransaction autoTransaction)
158{
159 if (!m_isValid)
160 return {};
161
162 if (!m_inAutoTransaction && autoTransaction == StartAutoTransaction) {
163 qCDebug(QT_NFC_PCSC) << "Starting transaction";
164
165 // FIXME: Is there a timeout on this?
166 auto ret = SCardBeginTransaction(m_handle);
167 if (ret != SCARD_S_SUCCESS) {
168 qCWarning(QT_NFC_PCSC) << "SCardBeginTransaction failed:" << QPcsc::errorMessage(ret);
170 return {};
171 }
172 m_inAutoTransaction = true;
173 m_keepAliveTimer->start();
174 }
175
176 QPcsc::RawCommandResult result;
177 result.response.resize(0xFFFF + 2);
178 DWORD recvLength = result.response.size();
179
180 qCDebug(QT_NFC_PCSC) << "TX:" << command.toHex(':');
181
182 result.ret = SCardTransmit(m_handle, &m_ioPci, reinterpret_cast<LPCBYTE>(command.constData()),
183 command.size(), nullptr,
184 reinterpret_cast<LPBYTE>(result.response.data()), &recvLength);
185 if (result.ret != SCARD_S_SUCCESS) {
186 qCWarning(QT_NFC_PCSC) << "SCardTransmit failed:" << QPcsc::errorMessage(result.ret);
187 result.response.clear();
189 } else {
190 result.response.resize(recvLength);
191 qCDebug(QT_NFC_PCSC) << "RX:" << result.response.toHex(':');
192 }
193
194 return result;
195}
196
197void QPcscCard::onKeepAliveTimeout()
198{
199 if (!m_isValid || !m_inAutoTransaction) {
200 m_keepAliveTimer->stop();
201 return;
202 }
203
205}
206
208{
209 QByteArray command = QCommandApdu::build(0xFF, QCommandApdu::GetData, 0x00, 0x00, {}, 256);
210
211 // Atomic command, no need for transaction.
212 QResponseApdu res(sendCommand(command, NoAutoTransaction).response);
213 if (!res.isOk())
214 return {};
215 return res.data();
216}
217
218void QPcscCard::onReadNdefMessagesRequest(const QNearFieldTarget::RequestId &request)
219{
220 if (!m_isValid) {
221 Q_EMIT requestCompleted(request, QNearFieldTarget::ConnectionError, {});
222 return;
223 }
224
225 if (!m_supportsNdef) {
226 Q_EMIT requestCompleted(request, QNearFieldTarget::UnsupportedError, {});
227 return;
228 }
229
230 Transaction transaction(this);
231
232 auto nextState = m_tagDetectionFsm->readMessages();
233
234 while (true) {
235 if (nextState == QNdefAccessFsm::SendCommand) {
236 auto command = m_tagDetectionFsm->getCommand(nextState);
237
238 if (nextState == QNdefAccessFsm::ProvideResponse) {
239 auto result = sendCommand(command, NoAutoTransaction);
240 nextState = m_tagDetectionFsm->provideResponse(result.response);
241 }
242 } else if (nextState == QNdefAccessFsm::GetMessage) {
243 auto message = m_tagDetectionFsm->getMessage(nextState);
244 Q_EMIT ndefMessageRead(message);
245 } else {
246 break;
247 }
248 }
249
250 qCDebug(QT_NFC_PCSC) << "Final state:" << nextState;
251 auto errorCode = (nextState == QNdefAccessFsm::Done) ? QNearFieldTarget::NoError
252 : QNearFieldTarget::NdefReadError;
253 Q_EMIT requestCompleted(request, errorCode, {});
254}
255
256/*
257 Ends the persistent transaction and resets the card if sendCommand() was
258 used by user.
259
260 Resetting the card ensures that the current state of the card does not get
261 shared with other processes.
262*/
263void QPcscCard::onDisconnectRequest()
264{
265 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
266
267 if (!m_isValid)
268 return;
269
270 LONG ret;
271 if (m_inAutoTransaction) {
272 // NOTE: PCSCLite does not automatically release transaction in
273 // SCardReconnect(): https://salsa.debian.org/rousseau/PCSC/-/issues/11
274 ret = SCardEndTransaction(m_handle, SCARD_RESET_CARD);
275 if (ret != SCARD_S_SUCCESS) {
276 qCWarning(QT_NFC_PCSC) << "SCardEndTransaction failed:" << QPcsc::errorMessage(ret);
278 return;
279 }
280
281 m_inAutoTransaction = false;
282 }
283
284 DWORD activeProtocol;
285 ret = SCardReconnect(m_handle, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
286 SCARD_LEAVE_CARD, &activeProtocol);
287 if (ret != SCARD_S_SUCCESS) {
288 qCWarning(QT_NFC_PCSC) << "SCardReconnect failed:" << QPcsc::errorMessage(ret);
290 return;
291 }
292
293 Q_EMIT disconnected();
294}
295
297{
298 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
300}
301
302void QPcscCard::onSendCommandRequest(const QNearFieldTarget::RequestId &request,
303 const QByteArray &command)
304{
305 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
306
307 if (!m_isValid) {
308 Q_EMIT requestCompleted(request, QNearFieldTarget::ConnectionError, {});
309 return;
310 }
311
312 auto result = sendCommand(command, StartAutoTransaction);
313 if (result.isOk())
314 Q_EMIT requestCompleted(request, QNearFieldTarget::NoError, result.response);
315 else
316 Q_EMIT requestCompleted(request, QNearFieldTarget::CommandError, {});
317}
318
319void QPcscCard::onWriteNdefMessagesRequest(const QNearFieldTarget::RequestId &request,
320 const QList<QNdefMessage> &messages)
321{
322 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
323
324 if (!m_isValid) {
325 Q_EMIT requestCompleted(request, QNearFieldTarget::ConnectionError, {});
326 return;
327 }
328
329 if (!m_supportsNdef) {
330 Q_EMIT requestCompleted(request, QNearFieldTarget::UnsupportedError, {});
331 return;
332 }
333
334 Transaction transaction(this);
335
336 auto nextState = m_tagDetectionFsm->writeMessages(messages);
337
338 while (nextState == QNdefAccessFsm::SendCommand) {
339 auto command = m_tagDetectionFsm->getCommand(nextState);
340 if (nextState == QNdefAccessFsm::ProvideResponse) {
341 auto result = sendCommand(command, NoAutoTransaction);
342 nextState = m_tagDetectionFsm->provideResponse(result.response);
343 }
344 }
345
346 auto errorCode = (nextState == QNdefAccessFsm::Done) ? QNearFieldTarget::NoError
347 : QNearFieldTarget::NdefWriteError;
348 Q_EMIT requestCompleted(request, errorCode, {});
349}
350
351/*
352 Enable automatic card deletion when the connection is closed by the user
353 or the card otherwise becomes unavailable.
354
355 The automatic deletion is prevented initially so that
356 QNearFieldManagerPrivate can safely establish connections between this
357 object and a QNearFieldTargetPrivate proxy.
358*/
360{
361 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
362
363 m_autodelete = true;
364 if (!m_isValid)
365 deleteLater();
366}
367
368/*
369 Check if the card is still present in the reader.
370
371 Invalidates the object if card is not present.
372*/
374{
375 qCDebug(QT_NFC_PCSC) << Q_FUNC_INFO;
376
377 if (!m_isValid)
378 return false;
379
380 DWORD state;
381 auto ret = SCardStatus(m_handle, nullptr, nullptr, &state, nullptr, nullptr, nullptr);
382 if (ret != SCARD_S_SUCCESS) {
383 qCWarning(QT_NFC_PCSC) << "SCardStatus failed:" << QPcsc::errorMessage(ret);
385 return false;
386 }
387
388 qCDebug(QT_NFC_PCSC) << "State:" << Qt::hex << state;
389
390 return (state & SCARD_PRESENT) != 0;
391}
392
394{
395 if (!m_isValid)
396 return 0;
397
398 // Maximum standard APDU length
399 static constexpr int DefaultMaxInputLength = 261;
400
401 uint32_t maxInput;
402 DWORD attrSize = sizeof(maxInput);
403 auto ret = SCardGetAttrib(m_handle, SCARD_ATTR_MAXINPUT, reinterpret_cast<LPBYTE>(&maxInput),
404 &attrSize);
405 if (ret != SCARD_S_SUCCESS) {
406 qCDebug(QT_NFC_PCSC) << "SCardGetAttrib failed:" << QPcsc::errorMessage(ret);
407 return DefaultMaxInputLength;
408 }
409
410 if (attrSize != sizeof(maxInput)) {
411 qCWarning(QT_NFC_PCSC) << "Unexpected attribute size for SCARD_ATTR_MAXINPUT:" << attrSize;
412 return DefaultMaxInputLength;
413 }
414
415 return static_cast<int>(maxInput);
416}
417
418QT_END_NAMESPACE
void onReadNdefMessagesRequest(const QNearFieldTarget::RequestId &request)
QByteArray readUid()
void invalidate()
bool isValid() const
Definition qpcsccard_p.h:34
void onTargetDestroyed()
bool checkCardPresent()
void onWriteNdefMessagesRequest(const QNearFieldTarget::RequestId &request, const QList< QNdefMessage > &messages)
void onSendCommandRequest(const QNearFieldTarget::RequestId &request, const QByteArray &command)
int readMaxInputLength()
Q_INVOKABLE void enableAutodelete()