1 /*!
2  * \brief Unit tests for \ref ServerMessageHandlerImpl
3  *
4  * \copyright Copyright (c) 2017-2021 Governikus GmbH & Co. KG, Germany
5  */
6 
7 #include "ServerMessageHandler.h"
8 
9 #include "AppSettings.h"
10 #include "LogHandler.h"
11 #include "messages/IfdConnect.h"
12 #include "messages/IfdConnectResponse.h"
13 #include "messages/IfdDisconnect.h"
14 #include "messages/IfdDisconnectResponse.h"
15 #include "messages/IfdError.h"
16 #include "messages/IfdEstablishContext.h"
17 #include "messages/IfdEstablishContextResponse.h"
18 #include "messages/IfdEstablishPaceChannel.h"
19 #include "messages/IfdEstablishPaceChannelResponse.h"
20 #include "messages/IfdModifyPinResponse.h"
21 #include "messages/IfdStatus.h"
22 #include "messages/IfdTransmit.h"
23 #include "messages/IfdTransmitResponse.h"
24 
25 #include "MockCardConnectionWorker.h"
26 #include "MockDataChannel.h"
27 #include "MockReaderManagerPlugIn.h"
28 #include "TestFileHelper.h"
29 
30 #include <QSignalSpy>
31 #include <QtTest>
32 
33 Q_IMPORT_PLUGIN(MockReaderManagerPlugIn)
34 
35 using namespace governikus;
36 
37 Q_DECLARE_METATYPE(StatusCode)
38 Q_DECLARE_METATYPE(ECardApiResult::Minor)
39 
40 class MockRemoteDispatcherServer
41 	: public RemoteDispatcherServer
42 {
43 	Q_OBJECT
44 	QSharedPointer<const RemoteMessage> mMessage;
45 
46 	public:
MockRemoteDispatcherServer(const QSharedPointer<DataChannel> & pDataChannel)47 		explicit MockRemoteDispatcherServer(const QSharedPointer<DataChannel>& pDataChannel)
48 			: RemoteDispatcherServer(pDataChannel)
49 		{
50 		}
51 
52 
send(const QSharedPointer<const RemoteMessage> & pMessage)53 		Q_INVOKABLE void send(const QSharedPointer<const RemoteMessage>& pMessage) override
54 		{
55 			RemoteDispatcherServer::send(pMessage);
56 			mMessage = pMessage;
57 		}
58 
59 
getMessage()60 		QSharedPointer<const RemoteMessage> getMessage()
61 		{
62 			return mMessage;
63 		}
64 
65 
66 };
67 
68 class test_ServerMessageHandler
69 	: public QObject
70 {
71 	Q_OBJECT
72 
73 	private:
74 		QSharedPointer<MockDataChannel> mDataChannel;
75 		QPointer<MockRemoteDispatcherServer> mRemoteDispatcher;
76 
77 
removeReaderAndConsumeMessages(const QString & pReaderName)78 		void removeReaderAndConsumeMessages(const QString& pReaderName)
79 		{
80 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
81 			MockReaderManagerPlugIn::getInstance().removeReader(pReaderName);
82 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
83 		}
84 
85 
ensureContext(QString & pContextHandle)86 		void ensureContext(QString& pContextHandle)
87 		{
88 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
89 
90 			const QByteArray establishContextMsg("{\n"
91 												 "    \"msg\": \"IFDEstablishContext\",\n"
92 												 "    \"Protocol\": \"IFDInterface_WebSocket_v2\",\n"
93 												 "    \"UDName\": \"MAC-MINI\"\n"
94 												 "}");
95 
96 			mDataChannel->onReceived(establishContextMsg);
97 
98 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
99 			const QList<QVariant>& establishContextResponseArguments = sendSpy.last();
100 
101 			const QVariant establishContextResponseVariant = establishContextResponseArguments.at(0);
102 			QVERIFY(establishContextResponseVariant.canConvert<QByteArray>());
103 			const IfdEstablishContextResponse establishContextResponse(RemoteMessage::parseByteArray(establishContextResponseVariant.toByteArray()));
104 			QVERIFY(!establishContextResponse.isIncomplete());
105 			QCOMPARE(establishContextResponse.getType(), RemoteCardMessageType::IFDEstablishContextResponse);
106 
107 			pContextHandle = establishContextResponse.getContextHandle();
108 			QVERIFY(!pContextHandle.isEmpty());
109 		}
110 
111 	private Q_SLOTS:
initTestCase()112 		void initTestCase()
113 		{
114 			Env::getSingleton<LogHandler>()->init();
115 			const auto readerManager = Env::getSingleton<ReaderManager>();
116 			readerManager->init();
117 			readerManager->isScanRunning(); // just to wait until initialization finished
118 
119 			Env::setCreator<RemoteDispatcherServer*>(std::function<RemoteDispatcherServer* (const QSharedPointer<DataChannel>& pDataChannel)>([this](const QSharedPointer<DataChannel>){
120 						mRemoteDispatcher = new MockRemoteDispatcherServer(mDataChannel);
121 						return mRemoteDispatcher.data();
122 					}));
123 		}
124 
125 
cleanupTestCase()126 		void cleanupTestCase()
127 		{
128 			Env::getSingleton<ReaderManager>()->shutdown();
129 		}
130 
131 
init()132 		void init()
133 		{
134 			mDataChannel.reset(new MockDataChannel());
135 		}
136 
137 
cleanup()138 		void cleanup()
139 		{
140 			Env::getSingleton<LogHandler>()->resetBacklog();
141 		}
142 
143 
checkLogOnInvalidContext()144 		void checkLogOnInvalidContext()
145 		{
146 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
147 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
148 			IfdConnectResponse unexpectedMsg(QStringLiteral("RemoteReader"));
149 
150 			mDataChannel->onReceived(unexpectedMsg.toByteArray(IfdVersion::Version::latest, QStringLiteral("invalidConextHandle")));
151 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Invalid context handle received")));
152 		}
153 
154 
checkLogOnUnexpectedMessageWithContext()155 		void checkLogOnUnexpectedMessageWithContext()
156 		{
157 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
158 			QSignalSpy spyContextHandle(mDataChannel.data(), &MockDataChannel::fireSend);
159 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
160 
161 			IfdEstablishContext establishContext(IfdVersion::Version::v2, DeviceInfo::getName());
162 			mDataChannel->onReceived(establishContext.toByteArray(IfdVersion::Version::v2, QString()));
163 
164 			const QJsonDocument& doc = QJsonDocument::fromJson(spyContextHandle.at(0).at(0).toByteArray());
165 			const QString& contextHandle = doc.object().value(QLatin1String("ContextHandle")).toString();
166 
167 			IfdConnectResponse unexpectedMsg(QStringLiteral("RemoteReader"));
168 			mDataChannel->onReceived(unexpectedMsg.toByteArray(IfdVersion::Version::v2, contextHandle));
169 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Received an unexpected message of type: IFDConnectResponse")));
170 		}
171 
172 
checkLogOnInvalidMessage()173 		void checkLogOnInvalidMessage()
174 		{
175 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
176 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
177 
178 			mDataChannel->onReceived("{\n"
179 									 "    \"ContextHandle\": \"TestContext\",\n"
180 									 "    \"msg\": \"RANDOM_STUFF\"\n"
181 									 "}\n");
182 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Invalid messageType received")));
183 		}
184 
185 
testUnexpectedMessagesCauseAnIfdErrorMessage()186 		void testUnexpectedMessagesCauseAnIfdErrorMessage()
187 		{
188 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
189 			QString contextHandle;
190 			ensureContext(contextHandle);
191 
192 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
193 
194 			// We have a context handle: send unexpected messages and verify that an error message is sent back.
195 			sendSpy.clear();
196 
197 			ReaderInfo info(QStringLiteral("NFC Reader"));
198 			info.setMaxApduLength(500);
199 			info.setConnected(true);
200 			info.setBasicReader(true);
201 			Env::getSingleton<AppSettings>()->getRemoteServiceSettings().setPinPadMode(false);
202 			const IfdStatus status(info);
203 
204 			const QByteArrayList serverMessages({
205 						status.toByteArray(IfdVersion::Version::v2, contextHandle),
206 						IfdConnectResponse("NFC Reader").toByteArray(IfdVersion::Version::v2, contextHandle),
207 						IfdDisconnectResponse("NFC Reader").toByteArray(IfdVersion::Version::v2, contextHandle),
208 						IfdTransmitResponse("NFC Reader", "9000").toByteArray(IfdVersion::Version::v2, contextHandle),
209 						IfdEstablishPaceChannelResponse("My little Reader", EstablishPaceChannelOutput()).toByteArray(IfdVersion::Version::v2, contextHandle)
210 					});
211 			for (const auto& serverMessage : serverMessages)
212 			{
213 				mDataChannel->onReceived(serverMessage);
214 				QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
215 
216 				const QList<QVariant>& errorMessageArguments = sendSpy.last();
217 				const QVariant errorMessageVariant = errorMessageArguments.at(0);
218 				QVERIFY(errorMessageVariant.canConvert<QByteArray>());
219 
220 				const IfdError errorMessage(RemoteMessage::parseByteArray(errorMessageVariant.toByteArray()));
221 				QVERIFY(!errorMessage.isIncomplete());
222 				QCOMPARE(errorMessage.getType(), RemoteCardMessageType::IFDError);
223 				QCOMPARE(errorMessage.getContextHandle(), contextHandle);
224 				QCOMPARE(errorMessage.getSlotHandle(), QString());
225 				QVERIFY(errorMessage.resultHasError());
226 				QCOMPARE(errorMessage.getResultMinor(), ECardApiResult::Minor::AL_Unkown_API_Function);
227 
228 				sendSpy.clear();
229 			}
230 		}
231 
232 
ifdConnectForUnconnectedReaderSendsIFDL_UnknownSlot()233 		void ifdConnectForUnconnectedReaderSendsIFDL_UnknownSlot()
234 		{
235 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
236 			QString contextHandle;
237 			ensureContext(contextHandle);
238 
239 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
240 			const QByteArray ifdConnectMsg = IfdConnect(QStringLiteral("test-reader"), true).toByteArray(IfdVersion::Version::latest, contextHandle);
241 			mDataChannel->onReceived(ifdConnectMsg);
242 
243 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
244 
245 			const QList<QVariant>& connectResponseArguments = sendSpy.last();
246 			const QVariant connectResponseVariant = connectResponseArguments.at(0);
247 			QVERIFY(connectResponseVariant.canConvert<QByteArray>());
248 
249 			const IfdConnectResponse connectResponse(RemoteMessage::parseByteArray(connectResponseVariant.toByteArray()));
250 			QVERIFY(!connectResponse.isIncomplete());
251 			QCOMPARE(connectResponse.getType(), RemoteCardMessageType::IFDConnectResponse);
252 			QVERIFY(!connectResponse.getContextHandle().isEmpty());
253 			QCOMPARE(connectResponse.getSlotHandle(), QStringLiteral("test-reader"));
254 			QVERIFY(connectResponse.resultHasError());
255 			QCOMPARE(connectResponse.getResultMinor(), ECardApiResult::Minor::IFDL_UnknownSlot);
256 		}
257 
258 
ifdConnectForReaderWithoutCardSendsAL_Unknown_Error()259 		void ifdConnectForReaderWithoutCardSendsAL_Unknown_Error()
260 		{
261 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
262 			QString contextHandle;
263 			ensureContext(contextHandle);
264 
265 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
266 			MockReaderManagerPlugIn::getInstance().addReader("test-reader");
267 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
268 			sendSpy.clear();
269 
270 			const QByteArray ifdConnectMsg = IfdConnect(QStringLiteral("test-reader"), true).toByteArray(IfdVersion::Version::latest, contextHandle);
271 			mDataChannel->onReceived(ifdConnectMsg);
272 
273 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
274 
275 			const QList<QVariant>& connectResponseArguments = sendSpy.last();
276 			const QVariant connectResponseVariant = connectResponseArguments.at(0);
277 			QVERIFY(connectResponseVariant.canConvert<QByteArray>());
278 
279 			const IfdConnectResponse connectResponse(RemoteMessage::parseByteArray(connectResponseVariant.toByteArray()));
280 			QVERIFY(!connectResponse.isIncomplete());
281 			QCOMPARE(connectResponse.getType(), RemoteCardMessageType::IFDConnectResponse);
282 			QVERIFY(!connectResponse.getContextHandle().isEmpty());
283 			QCOMPARE(connectResponse.getSlotHandle(), QStringLiteral("test-reader"));
284 			QVERIFY(connectResponse.resultHasError());
285 			QCOMPARE(connectResponse.getResultMinor(), ECardApiResult::Minor::AL_Unknown_Error);
286 
287 			removeReaderAndConsumeMessages(QStringLiteral("test-reader"));
288 		}
289 
290 
ifdConnectForReaderWithConnectedCardSendsIFDL_IFD_SharingViolation()291 		void ifdConnectForReaderWithConnectedCardSendsIFDL_IFD_SharingViolation()
292 		{
293 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
294 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
295 			QString contextHandle;
296 			ensureContext(contextHandle);
297 
298 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
299 
300 			MockReader* reader = MockReaderManagerPlugIn::getInstance().addReader("test-reader");
301 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
302 			reader->setCard(MockCardConfig());
303 			QTRY_COMPARE(sendSpy.count(), 2); // clazy:exclude=qstring-allocations
304 
305 			const CardInfo cardInfo(CardType::EID_CARD, QSharedPointer<const EFCardAccess>(), 3, true);
306 			ReaderInfo info = reader->getReaderInfo();
307 			info.setCardInfo(cardInfo);
308 			reader->setReaderInfo(info);
309 			QTRY_COMPARE(sendSpy.count(), 3); // clazy:exclude=qstring-allocations
310 			sendSpy.clear();
311 
312 			const QByteArray ifdConnectMsg = IfdConnect(QStringLiteral("test-reader"), true).toByteArray(IfdVersion::Version::latest, contextHandle);
313 			mDataChannel->onReceived(ifdConnectMsg);
314 
315 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
316 
317 			const QList<QVariant>& connectResponse1Arguments = sendSpy.last();
318 			const QVariant connectResponse1Variant = connectResponse1Arguments.at(0);
319 			QVERIFY(connectResponse1Variant.canConvert<QByteArray>());
320 
321 			const IfdConnectResponse connectResponse1(RemoteMessage::parseByteArray(connectResponse1Variant.toByteArray()));
322 			QVERIFY(!connectResponse1.isIncomplete());
323 			QCOMPARE(connectResponse1.getType(), RemoteCardMessageType::IFDConnectResponse);
324 			QVERIFY(!connectResponse1.getContextHandle().isEmpty());
325 			QVERIFY(!connectResponse1.getSlotHandle().isEmpty());
326 
327 			QVERIFY(!connectResponse1.resultHasError());
328 			QCOMPARE(connectResponse1.getResultMinor(), ECardApiResult::Minor::null);
329 
330 			sendSpy.clear();
331 
332 			// Card connected, try to connect a second time and get an error.
333 			mDataChannel->onReceived(ifdConnectMsg);
334 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
335 
336 			const QList<QVariant>& connectResponse2Arguments = sendSpy.last();
337 			const QVariant connectResponse2Variant = connectResponse2Arguments.at(0);
338 			QVERIFY(connectResponse2Variant.canConvert<QByteArray>());
339 
340 			const IfdConnectResponse connectResponse2(RemoteMessage::parseByteArray(connectResponse2Variant.toByteArray()));
341 			QVERIFY(!connectResponse2.isIncomplete());
342 			QCOMPARE(connectResponse2.getType(), RemoteCardMessageType::IFDConnectResponse);
343 			QVERIFY(!connectResponse2.getContextHandle().isEmpty());
344 			QCOMPARE(connectResponse2.getSlotHandle(), QStringLiteral("test-reader"));
345 			QVERIFY(connectResponse2.resultHasError());
346 			QCOMPARE(connectResponse2.getResultMinor(), ECardApiResult::Minor::IFDL_IFD_SharingViolation);
347 
348 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Card is already connected \"test-reader\"")));
349 
350 			removeReaderAndConsumeMessages(QStringLiteral("test-reader"));
351 		}
352 
353 
ifdDisconnectForReaderWithConnectedCardSendsCorrectResponse()354 		void ifdDisconnectForReaderWithConnectedCardSendsCorrectResponse()
355 		{
356 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
357 			QString contextHandle;
358 			ensureContext(contextHandle);
359 
360 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
361 
362 			MockReader* reader = MockReaderManagerPlugIn::getInstance().addReader("test-reader");
363 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
364 			reader->setCard(MockCardConfig());
365 			QTRY_COMPARE(sendSpy.count(), 2); // clazy:exclude=qstring-allocations
366 			const CardInfo cardInfo(CardType::EID_CARD, QSharedPointer<const EFCardAccess>(), 3, true);
367 			ReaderInfo info = reader->getReaderInfo();
368 			info.setCardInfo(cardInfo);
369 			reader->setReaderInfo(info);
370 			QTRY_COMPARE(sendSpy.count(), 3); // clazy:exclude=qstring-allocations
371 			sendSpy.clear();
372 
373 			const QByteArray ifdConnectMsg = IfdConnect(QStringLiteral("test-reader"), true).toByteArray(IfdVersion::Version::latest, contextHandle);
374 			mDataChannel->onReceived(ifdConnectMsg);
375 
376 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
377 
378 			const QList<QVariant>& connectResponseArguments = sendSpy.last();
379 			const QVariant connectResponseVariant = connectResponseArguments.at(0);
380 			QVERIFY(connectResponseVariant.canConvert<QByteArray>());
381 
382 			const IfdConnectResponse connectResponse(RemoteMessage::parseByteArray(connectResponseVariant.toByteArray()));
383 			QVERIFY(!connectResponse.isIncomplete());
384 			QCOMPARE(connectResponse.getType(), RemoteCardMessageType::IFDConnectResponse);
385 			QCOMPARE(connectResponse.getContextHandle(), contextHandle);
386 			QVERIFY(!connectResponse.getSlotHandle().isEmpty());
387 
388 			QVERIFY(!connectResponse.resultHasError());
389 			QCOMPARE(connectResponse.getResultMinor(), ECardApiResult::Minor::null);
390 
391 			sendSpy.clear();
392 
393 			// Card connected, try to disconnect.
394 			const QByteArray ifdDisconnectMsg = IfdDisconnect(connectResponse.getSlotHandle()).toByteArray(IfdVersion::Version::latest, contextHandle);
395 			mDataChannel->onReceived(ifdDisconnectMsg);
396 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
397 
398 			const QList<QVariant>& disconnectResponseArguments = sendSpy.last();
399 			const QVariant disconnectResponseVariant = disconnectResponseArguments.at(0);
400 			QVERIFY(disconnectResponseVariant.canConvert<QByteArray>());
401 
402 			const IfdDisconnectResponse disconnectResponse(RemoteMessage::parseByteArray(disconnectResponseVariant.toByteArray()));
403 			QVERIFY(!disconnectResponse.isIncomplete());
404 			QCOMPARE(disconnectResponse.getType(), RemoteCardMessageType::IFDDisconnectResponse);
405 			QCOMPARE(disconnectResponse.getContextHandle(), contextHandle);
406 			QCOMPARE(disconnectResponse.getSlotHandle(), connectResponse.getSlotHandle());
407 			QVERIFY(!disconnectResponse.resultHasError());
408 			QCOMPARE(disconnectResponse.getResultMinor(), ECardApiResult::Minor::null);
409 
410 			removeReaderAndConsumeMessages(QStringLiteral("test-reader"));
411 		}
412 
413 
ifdDisconnectForReaderWithWrongReaderNameSendsIFDL_InvalidSlotHandle()414 		void ifdDisconnectForReaderWithWrongReaderNameSendsIFDL_InvalidSlotHandle()
415 		{
416 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
417 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
418 			QString contextHandle;
419 			ensureContext(contextHandle);
420 
421 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
422 
423 			MockReader* reader = MockReaderManagerPlugIn::getInstance().addReader("test-reader");
424 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
425 			reader->setCard(MockCardConfig());
426 			QTRY_COMPARE(sendSpy.count(), 2); // clazy:exclude=qstring-allocations
427 			const CardInfo cardInfo(CardType::EID_CARD, QSharedPointer<const EFCardAccess>(), 3, true);
428 			ReaderInfo info = reader->getReaderInfo();
429 			info.setCardInfo(cardInfo);
430 			reader->setReaderInfo(info);
431 			QTRY_COMPARE(sendSpy.count(), 3); // clazy:exclude=qstring-allocations
432 			sendSpy.clear();
433 
434 			const QByteArray ifdConnectMsg = IfdConnect(QStringLiteral("test-reader"), true).toByteArray(IfdVersion::Version::latest, contextHandle);
435 			mDataChannel->onReceived(ifdConnectMsg);
436 
437 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
438 
439 			const QList<QVariant>& connectResponseArguments = sendSpy.last();
440 			const QVariant connectResponseVariant = connectResponseArguments.at(0);
441 			QVERIFY(connectResponseVariant.canConvert<QByteArray>());
442 
443 			const IfdConnectResponse connectResponse(RemoteMessage::parseByteArray(connectResponseVariant.toByteArray()));
444 			QVERIFY(!connectResponse.isIncomplete());
445 			QCOMPARE(connectResponse.getType(), RemoteCardMessageType::IFDConnectResponse);
446 			QCOMPARE(connectResponse.getContextHandle(), contextHandle);
447 			QVERIFY(!connectResponse.getSlotHandle().isEmpty());
448 
449 			QVERIFY(!connectResponse.resultHasError());
450 			QCOMPARE(connectResponse.getResultMinor(), ECardApiResult::Minor::null);
451 
452 			sendSpy.clear();
453 
454 			// Card connected, try to disconnect from wrong reader.
455 			const QByteArray ifdDisconnectMsg = IfdDisconnect(QStringLiteral("wrong-reader")).toByteArray(IfdVersion::Version::latest, contextHandle);
456 			mDataChannel->onReceived(ifdDisconnectMsg);
457 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
458 
459 			const QList<QVariant>& disconnectResponseArguments = sendSpy.last();
460 			const QVariant disconnectResponseVariant = disconnectResponseArguments.at(0);
461 			QVERIFY(disconnectResponseVariant.canConvert<QByteArray>());
462 
463 			const IfdDisconnectResponse disconnectResponse(RemoteMessage::parseByteArray(disconnectResponseVariant.toByteArray()));
464 			QVERIFY(!disconnectResponse.isIncomplete());
465 			QCOMPARE(disconnectResponse.getType(), RemoteCardMessageType::IFDDisconnectResponse);
466 			QCOMPARE(connectResponse.getContextHandle(), contextHandle);
467 			QCOMPARE(disconnectResponse.getSlotHandle(), QStringLiteral("wrong-reader"));
468 			QVERIFY(disconnectResponse.resultHasError());
469 			QCOMPARE(disconnectResponse.getResultMinor(), ECardApiResult::Minor::IFDL_InvalidSlotHandle);
470 
471 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Card is not connected \"wrong-reader\"")));
472 
473 			removeReaderAndConsumeMessages(QStringLiteral("test-reader"));
474 		}
475 
476 
ifdTransmitWithWrongReaderNameSendsIFDL_InvalidSlotHandle()477 		void ifdTransmitWithWrongReaderNameSendsIFDL_InvalidSlotHandle()
478 		{
479 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
480 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
481 			QString contextHandle;
482 			ensureContext(contextHandle);
483 
484 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
485 
486 			MockReader* reader = MockReaderManagerPlugIn::getInstance().addReader("test-reader");
487 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
488 			reader->setCard(MockCardConfig());
489 			QTRY_COMPARE(sendSpy.count(), 2); // clazy:exclude=qstring-allocations
490 			const CardInfo cardInfo(CardType::EID_CARD, QSharedPointer<const EFCardAccess>(), 3, true);
491 			ReaderInfo info = reader->getReaderInfo();
492 			info.setCardInfo(cardInfo);
493 			reader->setReaderInfo(info);
494 			QTRY_COMPARE(sendSpy.count(), 3); // clazy:exclude=qstring-allocations
495 			sendSpy.clear();
496 
497 			const QByteArray ifdConnectMsg = IfdConnect(QStringLiteral("test-reader"), true).toByteArray(IfdVersion::Version::latest, contextHandle);
498 			mDataChannel->onReceived(ifdConnectMsg);
499 
500 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
501 
502 			const QList<QVariant>& connectResponseArguments = sendSpy.last();
503 			const QVariant connectResponseVariant = connectResponseArguments.at(0);
504 			QVERIFY(connectResponseVariant.canConvert<QByteArray>());
505 
506 			const IfdConnectResponse connectResponse(RemoteMessage::parseByteArray(connectResponseVariant.toByteArray()));
507 			QVERIFY(!connectResponse.isIncomplete());
508 			QCOMPARE(connectResponse.getType(), RemoteCardMessageType::IFDConnectResponse);
509 			QCOMPARE(connectResponse.getContextHandle(), contextHandle);
510 			QVERIFY(!connectResponse.getSlotHandle().isEmpty());
511 
512 			QVERIFY(!connectResponse.resultHasError());
513 			QCOMPARE(connectResponse.getResultMinor(), ECardApiResult::Minor::null);
514 
515 			sendSpy.clear();
516 
517 			// Card connected, try to transmit to wrong reader.
518 			const QByteArray ifdTransmitMsg = IfdTransmit(QStringLiteral("wrong-reader"), QByteArray()).toByteArray(IfdVersion::Version::latest, contextHandle);
519 			mDataChannel->onReceived(ifdTransmitMsg);
520 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
521 
522 			const QList<QVariant>& transmitResponseArguments = sendSpy.last();
523 			const QVariant transmitResponseVariant = transmitResponseArguments.at(0);
524 			QVERIFY(transmitResponseVariant.canConvert<QByteArray>());
525 
526 			const IfdTransmitResponse transmitResponse(RemoteMessage::parseByteArray(transmitResponseVariant.toByteArray()));
527 			QVERIFY(!transmitResponse.isIncomplete());
528 			QCOMPARE(transmitResponse.getType(), RemoteCardMessageType::IFDTransmitResponse);
529 			QCOMPARE(transmitResponse.getContextHandle(), contextHandle);
530 			QCOMPARE(transmitResponse.getSlotHandle(), QStringLiteral("wrong-reader"));
531 			QVERIFY(transmitResponse.resultHasError());
532 			QCOMPARE(transmitResponse.getResultMinor(), ECardApiResult::Minor::IFDL_InvalidSlotHandle);
533 
534 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Card is not connected \"wrong-reader\"")));
535 
536 			removeReaderAndConsumeMessages(QStringLiteral("test-reader"));
537 		}
538 
539 
ifdEstablishPACEChannelWithWrongReaderNameSendsIFDL_InvalidSlotHandle()540 		void ifdEstablishPACEChannelWithWrongReaderNameSendsIFDL_InvalidSlotHandle()
541 		{
542 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
543 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
544 			QString contextHandle;
545 			ensureContext(contextHandle);
546 
547 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
548 
549 			MockReader* reader = MockReaderManagerPlugIn::getInstance().addReader("test-reader");
550 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
551 			reader->setCard(MockCardConfig());
552 			QTRY_COMPARE(sendSpy.count(), 2); // clazy:exclude=qstring-allocations
553 			const CardInfo cardInfo(CardType::EID_CARD, QSharedPointer<const EFCardAccess>(), 3, true);
554 			ReaderInfo info = reader->getReaderInfo();
555 			info.setCardInfo(cardInfo);
556 			reader->setReaderInfo(info);
557 			QTRY_COMPARE(sendSpy.count(), 3); // clazy:exclude=qstring-allocations
558 			sendSpy.clear();
559 
560 			const QByteArray ifdConnectMsg = IfdConnect(QStringLiteral("test-reader"), true).toByteArray(IfdVersion::Version::latest, contextHandle);
561 			mDataChannel->onReceived(ifdConnectMsg);
562 
563 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
564 
565 			const QList<QVariant>& connectResponseArguments = sendSpy.last();
566 			const QVariant connectResponseVariant = connectResponseArguments.at(0);
567 			QVERIFY(connectResponseVariant.canConvert<QByteArray>());
568 
569 			const IfdConnectResponse connectResponse(RemoteMessage::parseByteArray(connectResponseVariant.toByteArray()));
570 			QVERIFY(!connectResponse.isIncomplete());
571 			QCOMPARE(connectResponse.getType(), RemoteCardMessageType::IFDConnectResponse);
572 			QCOMPARE(connectResponse.getContextHandle(), contextHandle);
573 			QVERIFY(!connectResponse.getSlotHandle().isEmpty());
574 
575 			QVERIFY(!connectResponse.resultHasError());
576 			QCOMPARE(connectResponse.getResultMinor(), ECardApiResult::Minor::null);
577 
578 			sendSpy.clear();
579 
580 			// Card connected, try to establish PACE with the wrong reader.
581 			EstablishPaceChannel establishPaceChannel(PacePasswordId::PACE_PIN);
582 			const QByteArray ifdEstablishPACEChannelMsg = IfdEstablishPaceChannel(QStringLiteral("wrong-reader"), establishPaceChannel, 6).toByteArray(IfdVersion::Version::latest, contextHandle);
583 			mDataChannel->onReceived(ifdEstablishPACEChannelMsg);
584 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
585 
586 			const QList<QVariant>& ifdEstablishPACEChannelResponseArguments = sendSpy.last();
587 			const QVariant ifdEstablishPACEChannelResponseVariant = ifdEstablishPACEChannelResponseArguments.at(0);
588 			QVERIFY(ifdEstablishPACEChannelResponseVariant.canConvert<QByteArray>());
589 
590 			const IfdEstablishPaceChannelResponse ifdEstablishPACEChannelResponse(RemoteMessage::parseByteArray(ifdEstablishPACEChannelResponseVariant.toByteArray()));
591 			QVERIFY(!ifdEstablishPACEChannelResponse.isIncomplete());
592 			QCOMPARE(ifdEstablishPACEChannelResponse.getType(), RemoteCardMessageType::IFDEstablishPACEChannelResponse);
593 			QCOMPARE(ifdEstablishPACEChannelResponse.getContextHandle(), contextHandle);
594 			QCOMPARE(ifdEstablishPACEChannelResponse.getSlotHandle(), QStringLiteral("wrong-reader"));
595 			QVERIFY(ifdEstablishPACEChannelResponse.resultHasError());
596 			QCOMPARE(ifdEstablishPACEChannelResponse.getResultMinor(), ECardApiResult::Minor::IFDL_InvalidSlotHandle);
597 
598 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Card is not connected \"wrong-reader\"")));
599 
600 			removeReaderAndConsumeMessages(QStringLiteral("test-reader"));
601 		}
602 
603 
ifdEstablishPACEChannelWithBasicReaderNameSendsAL_Unknown_Error()604 		void ifdEstablishPACEChannelWithBasicReaderNameSendsAL_Unknown_Error()
605 		{
606 			const bool pinpadModeToSave = Env::getSingleton<AppSettings>()->getRemoteServiceSettings().getPinPadMode();
607 			Env::getSingleton<AppSettings>()->getRemoteServiceSettings().setPinPadMode(false);
608 
609 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
610 			ServerMessageHandlerImpl serverMessageHandler(mDataChannel);
611 			QString contextHandle;
612 			ensureContext(contextHandle);
613 
614 			QSignalSpy sendSpy(mDataChannel.data(), &MockDataChannel::fireSend);
615 
616 			MockReader* reader = MockReaderManagerPlugIn::getInstance().addReader("test-reader");
617 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
618 			reader->setCard(MockCardConfig());
619 			QTRY_COMPARE(sendSpy.count(), 2); // clazy:exclude=qstring-allocations
620 			const CardInfo cardInfo(CardType::EID_CARD, QSharedPointer<const EFCardAccess>(), 3, true);
621 			ReaderInfo info = reader->getReaderInfo();
622 			info.setCardInfo(cardInfo);
623 			reader->setReaderInfo(info);
624 			QTRY_COMPARE(sendSpy.count(), 3); // clazy:exclude=qstring-allocations
625 			sendSpy.clear();
626 
627 			const QByteArray ifdConnectMsg = IfdConnect(QStringLiteral("test-reader"), true).toByteArray(IfdVersion::Version::latest, contextHandle);
628 			mDataChannel->onReceived(ifdConnectMsg);
629 
630 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
631 
632 			const QList<QVariant>& connectResponseArguments = sendSpy.last();
633 			const QVariant connectResponseVariant = connectResponseArguments.at(0);
634 			QVERIFY(connectResponseVariant.canConvert<QByteArray>());
635 
636 			const IfdConnectResponse connectResponse(RemoteMessage::parseByteArray(connectResponseVariant.toByteArray()));
637 			QVERIFY(!connectResponse.isIncomplete());
638 			QCOMPARE(connectResponse.getType(), RemoteCardMessageType::IFDConnectResponse);
639 			QCOMPARE(connectResponse.getContextHandle(), contextHandle);
640 			QVERIFY(!connectResponse.getSlotHandle().isEmpty());
641 
642 			QVERIFY(!connectResponse.resultHasError());
643 			QCOMPARE(connectResponse.getResultMinor(), ECardApiResult::Minor::null);
644 
645 			sendSpy.clear();
646 
647 			// Card connected, try to establish PACE with basic reader while not in pinpad mode.
648 			const QByteArray ifdEstablishPACEChannelMsg = IfdEstablishPaceChannel(connectResponse.getSlotHandle(), EstablishPaceChannel(), 6).toByteArray(IfdVersion::Version::latest, contextHandle);
649 			mDataChannel->onReceived(ifdEstablishPACEChannelMsg);
650 			QTRY_COMPARE(sendSpy.count(), 1); // clazy:exclude=qstring-allocations
651 
652 			const QList<QVariant>& ifdEstablishPACEChannelResponseArguments = sendSpy.last();
653 			const QVariant ifdEstablishPACEChannelResponseVariant = ifdEstablishPACEChannelResponseArguments.at(0);
654 			QVERIFY(ifdEstablishPACEChannelResponseVariant.canConvert<QByteArray>());
655 
656 			const IfdEstablishPaceChannelResponse ifdEstablishPACEChannelResponse(RemoteMessage::parseByteArray(ifdEstablishPACEChannelResponseVariant.toByteArray()));
657 			QVERIFY(!ifdEstablishPACEChannelResponse.isIncomplete());
658 			QCOMPARE(ifdEstablishPACEChannelResponse.getType(), RemoteCardMessageType::IFDEstablishPACEChannelResponse);
659 			QCOMPARE(ifdEstablishPACEChannelResponse.getContextHandle(), contextHandle);
660 			QCOMPARE(ifdEstablishPACEChannelResponse.getSlotHandle(), connectResponse.getSlotHandle());
661 			QVERIFY(ifdEstablishPACEChannelResponse.resultHasError());
662 			QCOMPARE(ifdEstablishPACEChannelResponse.getResultMinor(), ECardApiResult::Minor::AL_Unknown_Error);
663 
664 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("EstablishPaceChannel is only available in pin pad mode.")));
665 
666 			removeReaderAndConsumeMessages(QStringLiteral("test-reader"));
667 			Env::getSingleton<AppSettings>()->getRemoteServiceSettings().setPinPadMode(pinpadModeToSave);
668 		}
669 
670 
test_handleIfdModifyPin()671 		void test_handleIfdModifyPin()
672 		{
673 			QSignalSpy logSpy(Env::getSingleton<LogHandler>()->getEventHandler(), &LogEventHandler::fireLog);
674 			const QByteArray message("{\n"
675 									 "    \"ContextHandle\": \"TestContext\",\n"
676 									 "    \"InputData\": \"abcd1234\",\n"
677 									 "    \"SlotHandle\": \"SlotHandle\",\n"
678 									 "    \"msg\": \"IFDModifyPIN\"\n"
679 									 "}\n");
680 
681 			const QJsonObject& obj = QJsonDocument::fromJson(message).object();
682 			ServerMessageHandlerImpl serverMsgHandler(mDataChannel);
683 			QSignalSpy spyModifyPin(&serverMsgHandler, &ServerMessageHandler::fireModifyPin);
684 			QString contextHandle;
685 			ensureContext(contextHandle);
686 
687 			QVERIFY(!TestFileHelper::containsLog(logSpy, QLatin1String("ModifyPin is only available in pin pad mode.")));
688 			Env::getSingleton<AppSettings>()->getRemoteServiceSettings().setPinPadMode(false);
689 			Q_EMIT mRemoteDispatcher->fireReceived(RemoteCardMessageType::IFDModifyPIN, obj, QString());
690 			QCOMPARE(mRemoteDispatcher->getMessage()->getType(), RemoteCardMessageType::IFDModifyPINResponse);
691 			const auto& msg1 = mRemoteDispatcher->getMessage().staticCast<const IfdModifyPinResponse>();
692 			QCOMPARE(msg1->getResultMinor(), ECardApiResult::Minor::AL_Unknown_Error);
693 			QCOMPARE(msg1->getSlotHandle(), "SlotHandle");
694 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("ModifyPin is only available in pin pad mode.")));
695 
696 			QVERIFY(!TestFileHelper::containsLog(logSpy, QLatin1String("Card is not connected")));
697 			Env::getSingleton<AppSettings>()->getRemoteServiceSettings().setPinPadMode(true);
698 			Q_EMIT mRemoteDispatcher->fireReceived(RemoteCardMessageType::IFDModifyPIN, obj, QString());
699 			const auto& msg2 = mRemoteDispatcher->getMessage().staticCast<const IfdModifyPinResponse>();
700 			QCOMPARE(msg2->getResultMinor(), ECardApiResult::Minor::IFDL_InvalidSlotHandle);
701 			QVERIFY(TestFileHelper::containsLog(logSpy, QLatin1String("Card is not connected")));
702 		}
703 
704 
test_SendModifyPinResponse_data()705 		void test_SendModifyPinResponse_data()
706 		{
707 			QTest::addColumn<StatusCode>("statusCode");
708 			QTest::addColumn<ECardApiResult::Minor>("minor");
709 
710 			QTest::newRow("success") << StatusCode::SUCCESS << ECardApiResult::Minor::null;
711 			QTest::newRow("empty") << StatusCode::EMPTY << ECardApiResult::Minor::IFDL_Terminal_NoCard;
712 			QTest::newRow("inputTimeout") << StatusCode::INPUT_TIMEOUT << ECardApiResult::Minor::IFDL_Timeout_Error;
713 			QTest::newRow("inputCancelled") << StatusCode::INPUT_CANCELLED << ECardApiResult::Minor::IFDL_CancellationByUser;
714 			QTest::newRow("passwordsDiffer") << StatusCode::PASSWORDS_DIFFER << ECardApiResult::Minor::IFDL_IO_RepeatedDataMismatch;
715 			QTest::newRow("passwordOutOfRange") << StatusCode::PASSWORD_OUTOF_RANGE << ECardApiResult::Minor::IFDL_IO_UnknownPINFormat;
716 			QTest::newRow("default") << StatusCode::INVALID << ECardApiResult::Minor::AL_Unknown_Error;
717 		}
718 
719 
test_SendModifyPinResponse()720 		void test_SendModifyPinResponse()
721 		{
722 			QFETCH(StatusCode, statusCode);
723 			QFETCH(ECardApiResult::Minor, minor);
724 
725 			ServerMessageHandlerImpl serverMsgHandler(mDataChannel);
726 			const QString slotHandle("Slot Handle");
727 			const ResponseApdu apdu(statusCode);
728 
729 			QString contextHandle;
730 			ensureContext(contextHandle);
731 			serverMsgHandler.sendModifyPinResponse(slotHandle, apdu);
732 			QCOMPARE(mRemoteDispatcher->getMessage()->getType(), RemoteCardMessageType::IFDModifyPINResponse);
733 			const auto& msg = mRemoteDispatcher->getMessage().staticCast<const IfdModifyPinResponse>();
734 			QCOMPARE(msg->getResultMinor(), minor);
735 			QCOMPARE(msg->getSlotHandle(), slotHandle);
736 		}
737 
738 
test_EstablishPaceChannelResponse_data()739 		void test_EstablishPaceChannelResponse_data()
740 		{
741 			QTest::addColumn<CardReturnCode>("returnCode");
742 			QTest::addColumn<ECardApiResult::Minor>("minor");
743 
744 			QTest::newRow("unknown") << CardReturnCode::UNKNOWN << ECardApiResult::Minor::AL_Unknown_Error;
745 			QTest::newRow("unknown") << CardReturnCode::CARD_NOT_FOUND << ECardApiResult::Minor::IFDL_Terminal_NoCard;
746 			QTest::newRow("default") << CardReturnCode::OK << ECardApiResult::Minor::null;
747 		}
748 
749 
test_EstablishPaceChannelResponse()750 		void test_EstablishPaceChannelResponse()
751 		{
752 			QFETCH(CardReturnCode, returnCode);
753 			QFETCH(ECardApiResult::Minor, minor);
754 
755 			const QString slotHandle("Slot Handle");
756 			EstablishPaceChannelOutput output;
757 			output.setPaceReturnCode(returnCode);
758 
759 			ServerMessageHandlerImpl serverMsgHandler(mDataChannel);
760 			QString contextHandle;
761 			ensureContext(contextHandle);
762 
763 			serverMsgHandler.sendEstablishPaceChannelResponse(slotHandle, output);
764 			QCOMPARE(mRemoteDispatcher->getMessage()->getType(), RemoteCardMessageType::IFDEstablishPACEChannelResponse);
765 			const auto& msg = mRemoteDispatcher->getMessage().staticCast<const IfdEstablishPaceChannelResponse>();
766 			QCOMPARE(msg->getResultMinor(), minor);
767 			QCOMPARE(msg->getSlotHandle(), slotHandle);
768 		}
769 
770 
771 };
772 
773 
774 QTEST_GUILESS_MAIN(test_ServerMessageHandler)
775 #include "test_ServerMessageHandler.moc"
776