1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtNfc module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtTest/QtTest> |
30 | |
31 | #include <private/qnearfieldmanager_emulator_p.h> |
32 | #include <qnearfieldmanager.h> |
33 | #include <qndefmessage.h> |
34 | #include <private/qnearfieldtagtype1_p.h> |
35 | #include <qndefnfctextrecord.h> |
36 | |
37 | QT_USE_NAMESPACE |
38 | |
39 | Q_DECLARE_METATYPE(QNearFieldTarget*) |
40 | |
41 | class tst_QNearFieldTagType1 : public QObject |
42 | { |
43 | Q_OBJECT |
44 | |
45 | public: |
46 | tst_QNearFieldTagType1(); |
47 | |
48 | private slots: |
49 | void init(); |
50 | void cleanup(); |
51 | |
52 | void staticMemoryModel(); |
53 | void dynamicMemoryModel(); |
54 | |
55 | void ndefMessages(); |
56 | |
57 | private: |
58 | void waitForMatchingTarget(); |
59 | |
60 | QNearFieldManagerPrivateImpl *emulatorBackend; |
61 | QNearFieldManager *manager; |
62 | QNearFieldTagType1 *target; |
63 | }; |
64 | |
65 | tst_QNearFieldTagType1::tst_QNearFieldTagType1() |
66 | : emulatorBackend(0), manager(0), target(0) |
67 | { |
68 | QDir::setCurrent(QLatin1String(SRCDIR)); |
69 | |
70 | qRegisterMetaType<QNdefMessage>(); |
71 | qRegisterMetaType<QNearFieldTarget *>(); |
72 | } |
73 | |
74 | void tst_QNearFieldTagType1::init() |
75 | { |
76 | emulatorBackend = new QNearFieldManagerPrivateImpl; |
77 | manager = new QNearFieldManager(emulatorBackend, 0); |
78 | } |
79 | |
80 | void tst_QNearFieldTagType1::cleanup() |
81 | { |
82 | emulatorBackend->reset(); |
83 | |
84 | delete manager; |
85 | manager = 0; |
86 | emulatorBackend = 0; |
87 | target = 0; |
88 | } |
89 | |
90 | void tst_QNearFieldTagType1::waitForMatchingTarget() |
91 | { |
92 | QSignalSpy targetDetectedSpy(manager, SIGNAL(targetDetected(QNearFieldTarget*))); |
93 | |
94 | manager->startTargetDetection(); |
95 | |
96 | QTRY_VERIFY(!targetDetectedSpy.isEmpty()); |
97 | |
98 | target = |
99 | qobject_cast<QNearFieldTagType1 *>(object: targetDetectedSpy.first().at(i: 0).value<QNearFieldTarget *>()); |
100 | |
101 | manager->stopTargetDetection(); |
102 | |
103 | QVERIFY(target); |
104 | |
105 | QCOMPARE(target->type(), QNearFieldTarget::NfcTagType1); |
106 | } |
107 | |
108 | void tst_QNearFieldTagType1::staticMemoryModel() |
109 | { |
110 | waitForMatchingTarget(); |
111 | |
112 | QVERIFY(target->accessMethods() & QNearFieldTarget::TagTypeSpecificAccess); |
113 | |
114 | QCOMPARE(target->version(), quint8(0x10)); |
115 | |
116 | // readIdentification() |
117 | { |
118 | QNearFieldTarget::RequestId id = target->readIdentification(); |
119 | QVERIFY(target->waitForRequestCompleted(id)); |
120 | |
121 | const QByteArray data = target->requestResponse(id).toByteArray(); |
122 | |
123 | QCOMPARE(data.length(), 6); |
124 | |
125 | quint8 hr0 = data.at(i: 0); |
126 | //quint8 hr1 = data.at(1); |
127 | const QByteArray uid4 = data.mid(index: 2, len: 4); |
128 | |
129 | // verify NfcTagType1 |
130 | QVERIFY(hr0 & 0x10); |
131 | |
132 | QCOMPARE(uid4, target->uid().left(4)); |
133 | } |
134 | |
135 | // readAll() |
136 | { |
137 | // read all reads the first 120 bytes, prepended with HR0 and HR1. |
138 | QNearFieldTarget::RequestId id = target->readAll(); |
139 | QVERIFY(target->waitForRequestCompleted(id)); |
140 | |
141 | const QByteArray data = target->requestResponse(id).toByteArray(); |
142 | QCOMPARE(data.length(), 122); |
143 | |
144 | // verify NfcTagType1. |
145 | QVERIFY(data.at(0) & 0x10); |
146 | |
147 | // NFC Magic Number means NDEF message present. |
148 | QCOMPARE(quint8(data.at(10)) == 0xe1, target->hasNdefMessage()); |
149 | } |
150 | |
151 | // readByte() |
152 | { |
153 | QNearFieldTarget::RequestId id = target->readAll(); |
154 | QVERIFY(target->waitForRequestCompleted(id)); |
155 | |
156 | const QByteArray data = target->requestResponse(id).toByteArray(); |
157 | |
158 | for (int i = 0; i < 120; ++i) { |
159 | id = target->readByte(address: i); |
160 | QVERIFY(target->waitForRequestCompleted(id)); |
161 | |
162 | quint8 byte = target->requestResponse(id).toUInt(); |
163 | |
164 | QCOMPARE(byte, quint8(data.at(i + 2))); |
165 | } |
166 | } |
167 | |
168 | // writeByte() |
169 | { |
170 | for (int i = 0; i < 8; ++i) { |
171 | QNearFieldTarget::RequestId id = target->readByte(address: i); |
172 | QVERIFY(target->waitForRequestCompleted(id)); |
173 | |
174 | quint8 byte = target->requestResponse(id).toUInt(); |
175 | |
176 | id = target->writeByte(address: i, data: 0x55); |
177 | QVERIFY(target->waitForRequestCompleted(id)); |
178 | QVERIFY(!target->requestResponse(id).isValid()); |
179 | |
180 | id = target->readByte(address: i); |
181 | QVERIFY(target->waitForRequestCompleted(id)); |
182 | |
183 | quint8 readByte = target->requestResponse(id).toUInt(); |
184 | QCOMPARE(readByte, byte); |
185 | } |
186 | |
187 | for (int i = 8; i < 104; ++i) { |
188 | // Write 0x55 |
189 | QNearFieldTarget::RequestId id = target->writeByte(address: i, data: 0x55); |
190 | QVERIFY(target->waitForRequestCompleted(id)); |
191 | QVERIFY(target->requestResponse(id).toBool()); |
192 | |
193 | id = target->readByte(address: i); |
194 | QVERIFY(target->waitForRequestCompleted(id)); |
195 | quint8 readByte = target->requestResponse(id).toUInt(); |
196 | QCOMPARE(readByte, quint8(0x55)); |
197 | |
198 | // Write 0xaa |
199 | id = target->writeByte(address: i, data: 0xaa); |
200 | QVERIFY(target->waitForRequestCompleted(id)); |
201 | QVERIFY(target->requestResponse(id).toBool()); |
202 | |
203 | id = target->readByte(address: i); |
204 | QVERIFY(target->waitForRequestCompleted(id)); |
205 | readByte = target->requestResponse(id).toUInt(); |
206 | QCOMPARE(readByte, quint8(0xaa)); |
207 | |
208 | // Write 0x55 without erase, result should be 0xff |
209 | id = target->writeByte(address: i, data: 0x55, mode: QNearFieldTagType1::WriteOnly); |
210 | QVERIFY(target->waitForRequestCompleted(id)); |
211 | QVERIFY(target->requestResponse(id).toBool()); |
212 | |
213 | id = target->readByte(address: i); |
214 | QVERIFY(target->waitForRequestCompleted(id)); |
215 | readByte = target->requestResponse(id).toUInt(); |
216 | QCOMPARE(readByte, quint8(0xff)); |
217 | } |
218 | |
219 | for (int i = 104; i < 120; ++i) { |
220 | QNearFieldTarget::RequestId id = target->readByte(address: i); |
221 | QVERIFY(target->waitForRequestCompleted(id)); |
222 | |
223 | quint8 byte = target->requestResponse(id).toUInt(); |
224 | |
225 | id = target->writeByte(address: i, data: 0x55); |
226 | QVERIFY(target->waitForRequestCompleted(id)); |
227 | QVERIFY(!target->requestResponse(id).isValid()); |
228 | |
229 | id = target->readByte(address: i); |
230 | QVERIFY(target->waitForRequestCompleted(id)); |
231 | |
232 | quint8 readByte = target->requestResponse(id).toUInt(); |
233 | QCOMPARE(readByte, byte); |
234 | } |
235 | } |
236 | } |
237 | |
238 | void tst_QNearFieldTagType1::dynamicMemoryModel() |
239 | { |
240 | bool testedStatic = false; |
241 | bool testedDynamic = false; |
242 | |
243 | QList<QByteArray> seenIds; |
244 | forever { |
245 | waitForMatchingTarget(); |
246 | |
247 | QNearFieldTarget::RequestId id = target->readIdentification(); |
248 | QVERIFY(target->waitForRequestCompleted(id)); |
249 | |
250 | const QByteArray data = target->requestResponse(id).toByteArray(); |
251 | if (seenIds.contains(t: data)) |
252 | break; |
253 | else |
254 | seenIds.append(t: data); |
255 | |
256 | quint8 hr0 = data.at(i: 0); |
257 | bool dynamic = (((hr0 & 0xf0) == 0x10) && ((hr0 & 0x0f) != 0x01)); |
258 | |
259 | if (dynamic) { |
260 | testedDynamic = true; |
261 | |
262 | // block 0, UID is locked |
263 | { |
264 | QNearFieldTarget::RequestId id = target->readBlock(blockAddress: 0x00); |
265 | QVERIFY(target->waitForRequestCompleted(id)); |
266 | |
267 | const QByteArray block = target->requestResponse(id).toByteArray(); |
268 | |
269 | QCOMPARE(target->uid(), block.left(7)); |
270 | QCOMPARE(quint8(block.at(7)), quint8(0x00)); |
271 | |
272 | id = target->writeBlock(blockAddress: 0x00, data: QByteArray(8, quint8(0x55))); |
273 | QVERIFY(target->waitForRequestCompleted(id)); |
274 | QVERIFY(!target->requestResponse(id).isValid()); |
275 | |
276 | QCOMPARE(target->uid(), block.left(7)); |
277 | QCOMPARE(quint8(block.at(7)), quint8(0x00)); |
278 | } |
279 | |
280 | // static data area |
281 | QNearFieldTarget::RequestId id = target->readSegment(segmentAddress: 0); |
282 | QVERIFY(target->waitForRequestCompleted(id)); |
283 | QByteArray segment = target->requestResponse(id).toByteArray(); |
284 | for (int i = 1; i < 0x0d; ++i) { |
285 | // Write 0x55 |
286 | id = target->writeBlock(blockAddress: i, data: QByteArray(8, quint8(0x55))); |
287 | QVERIFY(target->waitForRequestCompleted(id)); |
288 | QVERIFY(target->requestResponse(id).toBool()); |
289 | |
290 | id = target->readBlock(blockAddress: i); |
291 | QVERIFY(target->waitForRequestCompleted(id)); |
292 | QCOMPARE(target->requestResponse(id).toByteArray(), QByteArray(8, quint8(0x55))); |
293 | |
294 | segment.replace(index: i * 8, len: 8, s: QByteArray(8, quint8(0x55))); |
295 | |
296 | id = target->readSegment(segmentAddress: 0); |
297 | QVERIFY(target->waitForRequestCompleted(id)); |
298 | QCOMPARE(target->requestResponse(id).toByteArray(), segment); |
299 | |
300 | // Write 0xaa |
301 | id = target->writeBlock(blockAddress: i, data: QByteArray(8, quint8(0xaa))); |
302 | QVERIFY(target->waitForRequestCompleted(id)); |
303 | QVERIFY(target->requestResponse(id).toBool()); |
304 | |
305 | id = target->readBlock(blockAddress: i); |
306 | QVERIFY(target->waitForRequestCompleted(id)); |
307 | QCOMPARE(target->requestResponse(id).toByteArray(), QByteArray(8, quint8(0xaa))); |
308 | |
309 | segment.replace(index: i * 8, len: 8, s: QByteArray(8, quint8(0xaa))); |
310 | |
311 | id = target->readSegment(segmentAddress: 0); |
312 | QVERIFY(target->waitForRequestCompleted(id)); |
313 | QCOMPARE(target->requestResponse(id).toByteArray(), segment); |
314 | |
315 | // Write 0x55 without erase, result should be 0xff |
316 | id = target->writeBlock(blockAddress: i, data: QByteArray(8, quint8(0x55)), |
317 | mode: QNearFieldTagType1::WriteOnly); |
318 | QVERIFY(target->waitForRequestCompleted(id)); |
319 | QVERIFY(target->requestResponse(id).toBool()); |
320 | |
321 | id = target->readBlock(blockAddress: i); |
322 | QVERIFY(target->waitForRequestCompleted(id)); |
323 | QCOMPARE(target->requestResponse(id).toByteArray(), QByteArray(8, quint8(0xff))); |
324 | |
325 | segment.replace(index: i * 8, len: 8, s: QByteArray(8, quint8(0xff))); |
326 | |
327 | id = target->readSegment(segmentAddress: 0); |
328 | QVERIFY(target->waitForRequestCompleted(id)); |
329 | QCOMPARE(target->requestResponse(id).toByteArray(), segment); |
330 | } |
331 | |
332 | // static / dynamic reserved lock area |
333 | for (int i = 0x0d; i < 0x10; ++i) { |
334 | id = target->readBlock(blockAddress: i); |
335 | QVERIFY(target->waitForRequestCompleted(id)); |
336 | QByteArray block = target->requestResponse(id).toByteArray(); |
337 | |
338 | id = target->writeBlock(blockAddress: i, data: QByteArray(8, quint8(0x55))); |
339 | QVERIFY(target->waitForRequestCompleted(id)); |
340 | QVERIFY(!target->requestResponse(id).isValid()); |
341 | |
342 | id = target->readBlock(blockAddress: i); |
343 | QVERIFY(target->waitForRequestCompleted(id)); |
344 | QCOMPARE(target->requestResponse(id).toByteArray(), block); |
345 | } |
346 | } else { |
347 | testedStatic = true; |
348 | |
349 | for (int i = 0; i < 256; ++i) { |
350 | QNearFieldTarget::RequestId id = target->readBlock(blockAddress: i); |
351 | QVERIFY(target->waitForRequestCompleted(id)); |
352 | QVERIFY(!target->requestResponse(id).isValid()); |
353 | |
354 | id = target->writeBlock(blockAddress: i, data: QByteArray(8, quint8(0x55))); |
355 | QVERIFY(target->waitForRequestCompleted(id)); |
356 | QVERIFY(!target->requestResponse(id).isValid()); |
357 | } |
358 | for (int i = 0; i < 16; ++i) { |
359 | QNearFieldTarget::RequestId id = target->readSegment(segmentAddress: i); |
360 | QVERIFY(target->waitForRequestCompleted(id)); |
361 | QVERIFY(!target->requestResponse(id).isValid()); |
362 | } |
363 | } |
364 | } |
365 | |
366 | QVERIFY(testedStatic); |
367 | QVERIFY(testedDynamic); |
368 | } |
369 | |
370 | void tst_QNearFieldTagType1::ndefMessages() |
371 | { |
372 | QByteArray firstId; |
373 | forever { |
374 | waitForMatchingTarget(); |
375 | |
376 | QNearFieldTarget::RequestId id = target->readIdentification(); |
377 | QVERIFY(target->waitForRequestCompleted(id)); |
378 | |
379 | const QByteArray uid = target->requestResponse(id).toByteArray(); |
380 | if (firstId.isEmpty()) |
381 | firstId = uid; |
382 | else if (firstId == uid) |
383 | break; |
384 | |
385 | QVERIFY(target->hasNdefMessage()); |
386 | |
387 | QSignalSpy ndefMessageReadSpy(target, SIGNAL(ndefMessageRead(QNdefMessage))); |
388 | QSignalSpy requestCompletedSpy(target, |
389 | SIGNAL(requestCompleted(QNearFieldTarget::RequestId))); |
390 | QSignalSpy errorSpy(target, |
391 | SIGNAL(error(QNearFieldTarget::Error,QNearFieldTarget::RequestId))); |
392 | |
393 | QNearFieldTarget::RequestId readId = target->readNdefMessages(); |
394 | |
395 | QVERIFY(readId.isValid()); |
396 | |
397 | QNearFieldTarget::RequestId completedId; |
398 | |
399 | while (completedId != readId) { |
400 | QTRY_VERIFY(!requestCompletedSpy.isEmpty() && errorSpy.isEmpty()); |
401 | |
402 | completedId = |
403 | requestCompletedSpy.takeFirst().first().value<QNearFieldTarget::RequestId>(); |
404 | } |
405 | |
406 | QList<QNdefMessage> ndefMessages; |
407 | for (int i = 0; i < ndefMessageReadSpy.count(); ++i) |
408 | ndefMessages.append(t: ndefMessageReadSpy.at(i).first().value<QNdefMessage>()); |
409 | |
410 | QList<QNdefMessage> messages; |
411 | QNdefNfcTextRecord textRecord; |
412 | textRecord.setText(QStringLiteral("tst_QNearFieldTagType1::ndefMessages" )); |
413 | |
414 | QNdefMessage message; |
415 | message.append(t: textRecord); |
416 | |
417 | if (target->memorySize() > 120) { |
418 | QNdefRecord record; |
419 | record.setTypeNameFormat(QNdefRecord::ExternalRtd); |
420 | record.setType("org.qt-project:ndefMessagesTest" ); |
421 | record.setPayload(QByteArray(120, quint8(0x55))); |
422 | message.append(t: record); |
423 | } |
424 | |
425 | messages.append(t: message); |
426 | |
427 | requestCompletedSpy.clear(); |
428 | errorSpy.clear(); |
429 | |
430 | QSignalSpy ndefMessageWriteSpy(target, SIGNAL(ndefMessagesWritten())); |
431 | QNearFieldTarget::RequestId writeId = target->writeNdefMessages(messages); |
432 | |
433 | QVERIFY(writeId.isValid()); |
434 | |
435 | completedId = QNearFieldTarget::RequestId(); |
436 | |
437 | while (completedId != writeId) { |
438 | QTRY_VERIFY(!requestCompletedSpy.isEmpty() && errorSpy.isEmpty()); |
439 | |
440 | completedId = |
441 | requestCompletedSpy.takeFirst().first().value<QNearFieldTarget::RequestId>(); |
442 | } |
443 | |
444 | QVERIFY(!ndefMessageWriteSpy.isEmpty()); |
445 | |
446 | QVERIFY(target->hasNdefMessage()); |
447 | |
448 | ndefMessageReadSpy.clear(); |
449 | requestCompletedSpy.clear(); |
450 | errorSpy.clear(); |
451 | |
452 | readId = target->readNdefMessages(); |
453 | |
454 | QVERIFY(readId.isValid()); |
455 | |
456 | completedId = QNearFieldTarget::RequestId(); |
457 | |
458 | while (completedId != readId) { |
459 | QTRY_VERIFY(!requestCompletedSpy.isEmpty() && errorSpy.isEmpty()); |
460 | |
461 | completedId = |
462 | requestCompletedSpy.takeFirst().first().value<QNearFieldTarget::RequestId>(); |
463 | } |
464 | |
465 | QList<QNdefMessage> storedMessages; |
466 | for (int i = 0; i < ndefMessageReadSpy.count(); ++i) |
467 | storedMessages.append(t: ndefMessageReadSpy.at(i).first().value<QNdefMessage>()); |
468 | |
469 | QVERIFY(ndefMessages != storedMessages); |
470 | |
471 | QCOMPARE(messages, storedMessages); |
472 | } |
473 | } |
474 | |
475 | QTEST_MAIN(tst_QNearFieldTagType1) |
476 | |
477 | // Unset the moc namespace which is not required for the following include. |
478 | #undef QT_BEGIN_MOC_NAMESPACE |
479 | #define QT_BEGIN_MOC_NAMESPACE |
480 | #undef QT_END_MOC_NAMESPACE |
481 | #define QT_END_MOC_NAMESPACE |
482 | |
483 | #include "tst_qnearfieldtagtype1.moc" |
484 | |