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
qnearfieldtarget_ios.mm
Go to the documentation of this file.
1// Copyright (C) 2020 Governikus GmbH & Co. KG
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 "ios/qiosnfcndefsessiondelegate_p.h"
5#include "ios/qiosndefnotifier_p.h"
6
8
9#import <CoreNFC/NFCNDEFReaderSession.h>
10#import <CoreNFC/NFCReaderSession.h>
11#import <CoreNFC/NFCTagReaderSession.h>
12#import <CoreNFC/NFCISO7816Tag.h>
13#import <CoreNFC/NFCTag.h>
14
15#include <QtCore/qapplicationstatic.h>
16#include <QtCore/qloggingcategory.h>
17
19
20Q_APPLICATION_STATIC(ResponseProvider, responseProvider)
21
22void ResponseProvider::provideResponse(QNearFieldTarget::RequestId requestId, QNearFieldTarget::Error error, QByteArray recvBuffer) {
23 Q_EMIT responseReceived(requestId, error, recvBuffer);
24}
25
26void NfcDeleter::operator()(void *obj)
27{
28 id some = static_cast<id>(obj);
29
30 if ([some conformsToProtocol:@protocol(NFCNDEFTag)])
31 [static_cast<id<NFCNDEFTag>>(some) release];
32 else if ([some conformsToProtocol:@protocol(NFCTag)])
33 [static_cast<id<NFCTag>>(some) release];
34 else
35 Q_UNREACHABLE();
36}
37
38QNearFieldTargetPrivateImpl:: QNearFieldTargetPrivateImpl(void *tag, QObject *parent) :
39 QNearFieldTargetPrivate(parent),
40 nfcTag(tag)
41{
42 Q_ASSERT(nfcTag);
43
44 QObject::connect(this, &QNearFieldTargetPrivate::error, this, &QNearFieldTargetPrivateImpl::onTargetError);
45 QObject::connect(responseProvider, &ResponseProvider::responseReceived, this, &QNearFieldTargetPrivateImpl::onResponseReceived);
46 QObject::connect(&targetCheckTimer, &QTimer::timeout, this, &QNearFieldTargetPrivateImpl::onTargetCheck);
47 targetCheckTimer.start(500);
48}
49
50QNearFieldTargetPrivateImpl:: QNearFieldTargetPrivateImpl(void *delegate, void *tag, QObject *parent) :
52 nfcTag(tag)
53{
54 Q_ASSERT(delegate && tag);
55 Q_ASSERT([id(tag) conformsToProtocol:@protocol(NFCNDEFTag)]);
56
57 auto qtDelegate = static_cast<QIosNfcNdefSessionDelegate *>(sessionDelegate = delegate);
58 notifier = [qtDelegate ndefNotifier];
59 Q_ASSERT(notifier);
60
61 // The 'notifier' lives on a (potentially different, unspecified) thread,
62 // thus connection is 'queued'.
63 QObject::connect(notifier, &QNfcNdefNotifier::tagError, this,
64 &QNearFieldTargetPrivate::error, Qt::QueuedConnection);
65
66 QObject::connect(this, &QNearFieldTargetPrivate::error, this, &QNearFieldTargetPrivateImpl::onTargetError);
67 QObject::connect(&targetCheckTimer, &QTimer::timeout, this, &QNearFieldTargetPrivateImpl::onTargetCheck);
68
69 targetCheckTimer.start(500);
70}
71
73{
74}
75
77{
78 queue.clear();
79 ndefOperations.clear();
80
81 if (isNdefTag()) {
82 Q_ASSERT(notifier);
83
84 QObject::disconnect(notifier, nullptr, this, nullptr);
85 notifier = nullptr;
86 }
87
88 nfcTag.reset();
89 sessionDelegate = nil;
90
91 targetCheckTimer.stop();
92
93 QMetaObject::invokeMethod(this, [this]() {
94 Q_EMIT targetLost(this);
95 }, Qt::QueuedConnection);
96}
97
99{
100 if (!nfcTag || isNdefTag()) // NFCNDEFTag does not have this information ...
101 return {};
102
103 if (@available(iOS 13, *)) {
104 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
105 id<NFCISO7816Tag> iso7816Tag = tag.asNFCISO7816Tag;
106 if (iso7816Tag)
107 return QByteArray::fromNSData(iso7816Tag.identifier);
108 }
109
110 return {};
111}
112
114{
115 if (!nfcTag || isNdefTag()) // No information provided by NFCNDEFTag.
116 return QNearFieldTarget::ProprietaryTag;
117
118 if (@available(iOS 13, *)) {
119 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
120 id<NFCISO7816Tag> iso7816Tag = tag.asNFCISO7816Tag;
121
122 if (tag.type != NFCTagTypeISO7816Compatible || iso7816Tag == nil)
123 return QNearFieldTarget::ProprietaryTag;
124
125 if (iso7816Tag.historicalBytes != nil && iso7816Tag.applicationData == nil)
126 return QNearFieldTarget::NfcTagType4A;
127
128 if (iso7816Tag.historicalBytes == nil && iso7816Tag.applicationData != nil)
129 return QNearFieldTarget::NfcTagType4B;
130
131 return QNearFieldTarget::NfcTagType4;
132 }
133
134 return QNearFieldTarget::ProprietaryTag;
135}
136
138{
139 if (isNdefTag())
140 return QNearFieldTarget::NdefAccess;
141
142 if (@available(iOS 13, *)) {
143 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
144 if (tag && [tag conformsToProtocol:@protocol(NFCISO7816Tag)])
145 return QNearFieldTarget::TagTypeSpecificAccess;
146 }
147
148 return QNearFieldTarget::UnknownAccess;
149}
150
152{
153 if (accessMethods() & QNearFieldTarget::TagTypeSpecificAccess)
154 return 0xFEFF;
155
156 // TODO: check if 'capacity' of NFCNDEFTag can be used?
157 return 0;
158}
159
161{
162 QNearFieldTarget::RequestId requestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate());
163
164 if (!(accessMethods() & QNearFieldTarget::TagTypeSpecificAccess)) {
165 reportError(QNearFieldTarget::UnsupportedError, requestId);
166 return requestId;
167 }
168
169 queue.enqueue(std::pair(requestId, command));
170
171 if (!connect()) {
172 reportError(QNearFieldTarget::TargetOutOfRangeError, requestId);
173 return requestId;
174 }
175
176 onExecuteRequest();
177 return requestId;
178}
179
181{
182 return hasNDEFMessage;
183}
184
186{
187 hasNDEFMessage = false;
188
189 QNearFieldTarget::RequestId requestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate);
190
191 if (!(accessMethods() & QNearFieldTarget::NdefAccess) || !isNdefTag()) {
192 qCWarning(QT_IOS_NFC, "Target does not allow to read NDEF messages, "
193 "was not detected as NDEF tag by the reader session?");
194 reportError(QNearFieldTarget::UnsupportedError, requestId);
195 return requestId;
196 }
197
198 NdefOperation op;
199 op.type = NdefOperation::Read;
200 op.requestId = requestId;
201
202 ndefOperations.push_back(op);
203 onExecuteRequest();
204
205 return requestId;
206}
207
208QNearFieldTarget::RequestId QNearFieldTargetPrivateImpl::writeNdefMessages(const QList<QNdefMessage> &messages)
209{
210 auto requestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate);
211
212 if (!(accessMethods() & QNearFieldTarget::NdefAccess) || !isNdefTag()) {
213 qCWarning(QT_IOS_NFC, "Target does not allow to write NDEF messages, "
214 "was not detected as NDEF tag by the reader session?");
215 reportError(QNearFieldTarget::UnsupportedError, requestId);
216 return requestId;
217 }
218
219 if (messages.size() != 1) {
220 // The native framework does not allow to write 'messages', only _one_ message
221 // at a time. Not to multiply the complexity of having 'ndefOperations' queue
222 // with some queue inside the delegate's code (plus some unpredictable errors
223 // handling) - require a single message as a single request.
224 qCWarning(QT_IOS_NFC, "Only one NDEF message per request ID can be written");
225 reportError(QNearFieldTarget::UnsupportedError, requestId);
226 return requestId;
227 }
228
229 NdefOperation op;
230 op.type = NdefOperation::Write;
231 op.requestId = requestId;
232 op.message = messages.first();
233
234 ndefOperations.push_back(op);
235 onExecuteRequest();
236
237 return requestId;
238}
239
241{
242 if (requestInProgress.isValid())
243 return true;
244
245 const auto tagIsAvailable = [this](auto tag) {
246 return tag && (!connected || tag.available);
247 };
248
249 if (isNdefTag())
250 return tagIsAvailable(static_cast<id<NFCNDEFTag>>(nfcTag.get()));
251
252 if (@available(iOS 13, *))
253 return tagIsAvailable(static_cast<id<NFCTag>>(nfcTag.get()));
254
255 return false;
256}
257
258bool QNearFieldTargetPrivateImpl::connect()
259{
260 if (connected || requestInProgress.isValid())
261 return true;
262
263 if (isNdefTag())
264 return connected = true;
265
266 if (!isAvailable() || queue.isEmpty())
267 return false;
268
269 if (@available(iOS 13, *)) {
270 requestInProgress = queue.head().first;
271 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
272 NFCTagReaderSession* session = tag.session;
273 [session connectToTag: tag completionHandler: ^(NSError* error){
274 const int errorCode = error == nil ? -1 : error.code;
275 QMetaObject::invokeMethod(this, [this, errorCode] {
276 requestInProgress = QNearFieldTarget::RequestId();
277 if (errorCode == -1) {
278 connected = true;
279 justConnected = true;
280 onExecuteRequest();
281 } else {
282 const auto requestId = queue.dequeue().first;
283 reportError(
284 errorCode == NFCReaderError::NFCReaderErrorSecurityViolation
285 ? QNearFieldTarget::UnsupportedTargetError
286 : QNearFieldTarget::ConnectionError,
287 requestId);
288 invalidate();
289 }
290 });
291 }];
292 return true;
293 }
294
295 return false;
296}
297
298bool QNearFieldTargetPrivateImpl::isNdefTag() const
299{
300 const id tag = static_cast<id>(nfcTag.get());
301 if ([tag conformsToProtocol:@protocol(NFCMiFareTag)])
302 return false;
303 if ([tag conformsToProtocol:@protocol(NFCFeliCaTag)])
304 return false;
305 if ([tag conformsToProtocol:@protocol(NFCISO15693Tag)])
306 return false;
307 if ([tag conformsToProtocol:@protocol(NFCISO7816Tag)])
308 return false;
309 return [tag conformsToProtocol:@protocol(NFCNDEFTag)];
310}
311
312void QNearFieldTargetPrivateImpl::onTargetCheck()
313{
314 if (!isAvailable())
316}
317
318void QNearFieldTargetPrivateImpl::onTargetError(QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id)
319{
320 Q_UNUSED(id);
321
322 if (error == QNearFieldTarget::TimeoutError)
324}
325
326namespace {
327
328QNdefMessage ndefToQtNdefMessage(NFCNDEFMessage *nativeMessage)
329{
330 if (!nativeMessage)
331 return {};
332
333 QList<QNdefRecord> ndefRecords;
334 for (NFCNDEFPayload *ndefRecord in nativeMessage.records) {
335 QNdefRecord qtNdefRecord;
336 if (ndefRecord.typeNameFormat != NFCTypeNameFormatUnchanged) // Does not match anything in Qt.
337 qtNdefRecord.setTypeNameFormat(QNdefRecord::TypeNameFormat(ndefRecord.typeNameFormat));
338 if (ndefRecord.identifier)
339 qtNdefRecord.setId(QByteArray::fromNSData(ndefRecord.identifier));
340 if (ndefRecord.type)
341 qtNdefRecord.setType(QByteArray::fromNSData(ndefRecord.type));
342 if (ndefRecord.payload)
343 qtNdefRecord.setPayload(QByteArray::fromNSData(ndefRecord.payload));
344 ndefRecords.push_back(qtNdefRecord);
345 }
346
347 return QNdefMessage{ndefRecords};
348}
349
350} // Unnamed namespace.
351
352void QNearFieldTargetPrivateImpl::onExecuteRequest()
353{
354 if (!nfcTag || requestInProgress.isValid())
355 return;
356
357 if (isNdefTag()) {
358 if (ndefOperations.empty())
359 return;
360
361 auto *ndefDelegate = static_cast<QIosNfcNdefSessionDelegate *>(sessionDelegate);
362 Q_ASSERT(ndefDelegate);
363
364 Q_ASSERT(qt_Nfc_Queue()); // This is where callbacks get called.
365
366 const auto op = ndefOperations.front();
367 ndefOperations.pop_front();
368 requestInProgress = op.requestId;
369 auto requestId = requestInProgress; // Copy so we capture by value in the block.
370
371 id<NFCNDEFTag> ndefTag = static_cast<id<NFCNDEFTag>>(nfcTag.get());
372
373 std::unique_ptr<QNfcNdefNotifier> guard(new QNfcNdefNotifier);
374 auto *cbNotifier = guard.get();
375
376 QObject::connect(cbNotifier, &QNfcNdefNotifier::tagError, this,
377 &QNearFieldTargetPrivate::error, Qt::QueuedConnection);
378
379 if (op.type == NdefOperation::Read) {
380 QObject::connect(cbNotifier, &QNfcNdefNotifier::ndefMessageRead,
381 this, &QNearFieldTargetPrivateImpl::messageRead,
382 Qt::QueuedConnection);
383
384 // We call it here, but the callback will be executed on an unspecified thread.
385 [ndefTag readNDEFWithCompletionHandler:^(NFCNDEFMessage * _Nullable msg, NSError * _Nullable err) {
386 const std::unique_ptr<QNfcNdefNotifier> notifierGuard(cbNotifier);
387 if (err) {
388 NSLog(@"Reading NDEF messaged ended with error: %@", err);
389 emit cbNotifier->tagError(QNearFieldTarget::NdefReadError, requestId);
390 return;
391 }
392
393 const QNdefMessage ndefMessage(ndefToQtNdefMessage(msg));
394 emit cbNotifier->ndefMessageRead(ndefMessage, requestId);
395 }];
396 } else {
397 QObject::connect(cbNotifier, &QNfcNdefNotifier::ndefMessageWritten,
398 this, &QNearFieldTargetPrivateImpl::messageWritten,
399 Qt::QueuedConnection);
400
401 NSData *ndefData = op.message.toByteArray().toNSData(); // autoreleased.
402 Q_ASSERT(ndefData);
403
404 NFCNDEFMessage *ndefMessage = [NFCNDEFMessage ndefMessageWithData:ndefData]; // autoreleased.
405 Q_ASSERT(ndefMessage);
406
407 [ndefTag writeNDEF:ndefMessage completionHandler:^(NSError *err) {
408 const std::unique_ptr<QNfcNdefNotifier> notifierGuard(cbNotifier);
409 if (err) {
410 NSLog(@"Writing NDEF messaged ended with error: %@", err);
411 emit cbNotifier->tagError(QNearFieldTarget::NdefWriteError, requestId);
412 return;
413 }
414
415 emit cbNotifier->ndefMessageWritten(requestId);
416 }];
417 }
418 guard.release(); // Owned by the completion handler now.
419 return;
420 }
421
422 if (@available(iOS 13, *)) {
423 if (queue.isEmpty())
424 return;
425 const auto request = queue.dequeue();
426 requestInProgress = request.first;
427 const auto tag = static_cast<id<NFCISO7816Tag>>(nfcTag.get());
428 auto *apdu = [[[NFCISO7816APDU alloc] initWithData: request.second.toNSData()] autorelease];
429 [tag sendCommandAPDU: apdu completionHandler: ^(NSData* responseData, uint8_t sw1, uint8_t sw2, NSError* error){
430 QByteArray recvBuffer = QByteArray::fromNSData(responseData);
431 recvBuffer += static_cast<char>(sw1);
432 recvBuffer += static_cast<char>(sw2);
433 auto errorToReport = QNearFieldTarget::NoError;
434 if (error != nil)
435 {
436 switch (error.code) {
437 case NFCReaderError::NFCReaderTransceiveErrorSessionInvalidated:
438 case NFCReaderError::NFCReaderTransceiveErrorTagNotConnected:
439 if (justConnected) {
440 errorToReport = QNearFieldTarget::UnsupportedTargetError;
441 justConnected = false;
442 break;
443 }
444 Q_FALLTHROUGH();
445 default:
446 errorToReport = QNearFieldTarget::CommandError;
447 }
448 }
449 responseProvider->provideResponse(request.first, errorToReport, recvBuffer);
450 }];
451 }
452}
453
454void QNearFieldTargetPrivateImpl::onResponseReceived(QNearFieldTarget::RequestId requestId, QNearFieldTarget::Error error, QByteArray recvBuffer)
455{
456 if (requestInProgress != requestId)
457 return;
458
459 requestInProgress = QNearFieldTarget::RequestId();
460 if (error == QNearFieldTarget::NoError) {
461 setResponseForRequest(requestId, recvBuffer, true);
462 onExecuteRequest();
463 } else {
464 reportError(error, requestId);
466 }
467}
468
469void QNearFieldTargetPrivateImpl::messageRead(const QNdefMessage &message, QNearFieldTarget::RequestId requestId)
470{
471 hasNDEFMessage = message.size() != 0;
472
473 setResponseForRequest(requestId, message.toByteArray(), true);
474 requestInProgress = {}; // Invalidating, so we can execute the next one.
475 onExecuteRequest();
476
477 Q_EMIT ndefMessageRead(message);
478}
479
480void QNearFieldTargetPrivateImpl::messageWritten(QNearFieldTarget::RequestId requestId)
481{
482 requestInProgress = {}; // Invalidating, so we can execute the next one.
483 onExecuteRequest();
484
485 Q_EMIT requestCompleted(requestId);
486}
487
488QT_END_NAMESPACE
The QNdefMessage class provides an NFC NDEF message.
QNearFieldTarget::RequestId writeNdefMessages(const QList< QNdefMessage > &messages) override
QNearFieldTargetPrivateImpl(void *sessionDelegate, void *tag, QObject *parent=nullptr)
QNearFieldTarget::RequestId readNdefMessages() override
QNearFieldTarget::Type type() const override
QNearFieldTarget::RequestId sendCommand(const QByteArray &command) override
QNearFieldTarget::AccessMethods accessMethods() const override
QByteArray uid() const override
Combined button and popup list for selecting options.
void operator()(void *tag)