1/*! 2 * \copyright Copyright (c) 2015-2021 Governikus GmbH & Co. KG, Germany 3 */ 4 5#include "IosCard.h" 6 7#include "IosCardPointer.h" 8 9#include <QElapsedTimer> 10#include <QLoggingCategory> 11 12#import <CoreNFC/NFCISO7816Tag.h> 13#import <CoreNFC/NFCReaderSession.h> 14#import <CoreNFC/NFCTagReaderSession.h> 15 16 17using namespace governikus; 18 19 20Q_DECLARE_LOGGING_CATEGORY(card_nfc) 21 22 23IosCard::IosCard(IosCardPointer* const pCard) 24 : Card() 25 , mCard(pCard) 26 , mConnected(false) 27{ 28 qCDebug(card_nfc) << "Card created"; 29} 30 31 32IosCard::~IosCard() 33{ 34 delete mCard; 35} 36 37 38void IosCard::waitForRequestCompleted(const bool& pCondition) const 39{ 40 QElapsedTimer timer; 41 timer.start(); 42 do 43 { 44 if (pCondition) 45 { 46 break; 47 } 48 49 QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 1); 50 } 51 while (timer.elapsed() <= 500); 52} 53 54 55bool IosCard::isValid() const 56{ 57 if (@available(iOS 13, *)) 58 { 59 return mCard->mNfcTag && (!mConnected || mCard->mNfcTag.available); 60 } 61 62 return false; 63} 64 65 66void IosCard::invalidateTarget() 67{ 68 mCard->mNfcTag = nil; 69} 70 71 72CardReturnCode IosCard::connect() 73{ 74 if (!isValid()) 75 { 76 qCWarning(card_nfc) << "NearFieldTarget is no longer valid"; 77 return CardReturnCode::COMMAND_FAILED; 78 } 79 80 if (isConnected()) 81 { 82 qCCritical(card_nfc) << "Card is already connected"; 83 return CardReturnCode::OK; 84 } 85 86 if (@available(iOS 13, *)) 87 { 88 __block bool callbackDone = false; 89 90 NFCTagReaderSession* session = mCard->mNfcTag.session; 91 [session connectToTag: mCard->mNfcTag completionHandler: ^(NSError* error){ 92 if (error != nil) 93 { 94 invalidateTarget(); 95 qCDebug(card_nfc) << "Error during connect:" << error; 96 } 97 else 98 { 99 mConnected = true; 100 } 101 102 callbackDone = true; 103 }]; 104 105 waitForRequestCompleted(callbackDone); 106 } 107 108 if (!mConnected) 109 { 110 return CardReturnCode::COMMAND_FAILED; 111 } 112 113 return CardReturnCode::OK; 114} 115 116 117CardReturnCode IosCard::disconnect() 118{ 119 if (!isValid()) 120 { 121 qCWarning(card_nfc) << "NearFieldTarget is no longer valid"; 122 return CardReturnCode::COMMAND_FAILED; 123 } 124 125 if (!isConnected()) 126 { 127 qCCritical(card_nfc) << "Card is already disconnected"; 128 return CardReturnCode::COMMAND_FAILED; 129 } 130 131 mConnected = false; 132 return CardReturnCode::OK; 133} 134 135 136bool IosCard::isConnected() 137{ 138 return mConnected; 139} 140 141 142void IosCard::setProgressMessage(const QString& pMessage, int pProgress) 143{ 144 if (@available(iOS 13, *)) 145 { 146 QString message = generateProgressMessage(pMessage, pProgress); 147 NFCTagReaderSession* session = mCard->mNfcTag.session; 148 session.alertMessage = message.toNSString(); 149 } 150} 151 152 153ResponseApduResult IosCard::transmit(const CommandApdu& pCmd) 154{ 155 if (!isValid()) 156 { 157 qCWarning(card_nfc) << "NearFieldTarget is no longer valid"; 158 return {CardReturnCode::COMMAND_FAILED}; 159 } 160 161 qCDebug(card_nfc) << "Transmit command APDU:" << pCmd.getBuffer().toHex(); 162 163 const auto resultBuffer = QSharedPointer<QByteArray>::create(); // Don't use this inside of the Block 164 const QWeakPointer<QByteArray> weakBuffer = resultBuffer; 165 166 if (@available(iOS 13, *)) 167 { 168 __block bool callbackDone = false; 169 170 Q_ASSERT([mCard->mNfcTag conformsToProtocol:@protocol(NFCISO7816Tag)]); 171 const auto tag = static_cast<id<NFCISO7816Tag>>(mCard->mNfcTag); 172 auto* apdu = [[NFCISO7816APDU alloc] initWithData: pCmd.getBuffer().toNSData()]; 173 [tag sendCommandAPDU: apdu completionHandler: ^(NSData* responseData, uint8_t sw1, uint8_t sw2, NSError* error){ 174 // By referencing weakBuffer here, it will be copied into the Block. If the handler outlives the caller, resultBuffer won't exist anymore. 175 if (const auto recvBuffer = weakBuffer.lock()) 176 { 177 if (error == nil) 178 { 179 *recvBuffer = QByteArray::fromNSData(responseData); 180 *recvBuffer += static_cast<char>(sw1); 181 *recvBuffer += static_cast<char>(sw2); 182 qCDebug(card_nfc) << "Transmit response APDU:" << recvBuffer->toHex(); 183 } 184 else 185 { 186 invalidateTarget(); 187 qCDebug(card_nfc) << "Error during transmit:" << error; 188 } 189 190 callbackDone = true; 191 } 192 else 193 { 194 qCDebug(card_nfc) << "Caller doesn't exist anymore."; 195 } 196 }]; 197 198 waitForRequestCompleted(callbackDone); 199 } 200 201 if (resultBuffer->isEmpty()) 202 { 203 return {CardReturnCode::COMMAND_FAILED}; 204 } 205 206 return {CardReturnCode::OK, ResponseApdu(*resultBuffer)}; 207} 208