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