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