Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qnfctagtype4ndeffsm.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <QtCore/QtEndian>
6#include <QtCore/QLoggingCategory>
7
9
10Q_LOGGING_CATEGORY(QT_NFC_T4T, "qt.nfc.t4t")
11
12/*
13 NDEF support for NFC Type 4 tags.
14
15 Based on Type 4 Tag Operation Specification, Version 2.0 (T4TOP 2.0).
16*/
17
19{
20 // ID of the NDEF Tag Application
21 static constexpr uint8_t NtagApplicationIdV2[] { 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01 };
22 // Capability container file ID.
23 static constexpr uint8_t CapabilityContainerId[] { 0xE1, 0x03 };
24 // Shortcut for specifying zero length of NDEF message data.
25 static constexpr uint8_t ZeroLength[] { 0x00, 0x00 };
26
27 nextAction = ProvideResponse;
28
29 switch (m_currentState) {
30 case SelectApplicationForProbe:
31 case SelectApplicationForRead:
32 case SelectApplicationForWrite:
33 return QCommandApdu::build(0x00, QCommandApdu::Select, 0x04, 0x00,
34 QByteArrayView::fromArray(NtagApplicationIdV2), 256);
35 case SelectCCFile:
36 return QCommandApdu::build(0x00, QCommandApdu::Select, 0x00, 0x0C,
37 QByteArrayView::fromArray(CapabilityContainerId));
38 case ReadCCFile:
39 return QCommandApdu::build(0x00, QCommandApdu::ReadBinary, 0x00, 0x00, {}, 15);
40 case SelectNdefFileForRead:
41 case SelectNdefFileForWrite:
42 return QCommandApdu::build(0x00, QCommandApdu::Select, 0x00, 0x0C, m_ndefFileId);
43 case ReadNdefMessageLength:
44 return QCommandApdu::build(0x00, QCommandApdu::ReadBinary, 0x00, 0x00, {}, 2);
45 case ReadNdefMessage: {
46 uint16_t readSize = qMin(m_fileSize, m_maxReadSize);
47
48 return QCommandApdu::build(0x00, QCommandApdu::ReadBinary, m_fileOffset >> 8,
49 m_fileOffset & 0xFF, {}, readSize);
50 }
51 case ClearNdefLength:
52 m_fileOffset = 2;
53 m_fileSize = m_ndefData.size();
54 return QCommandApdu::build(0x00, QCommandApdu::UpdateBinary, 0x00, 0x00,
55 QByteArrayView::fromArray(ZeroLength));
56 case WriteNdefFile: {
57 uint16_t updateSize = qMin(m_fileSize, m_maxUpdateSize);
58 uint16_t fileOffset = m_fileOffset;
59
60 m_fileOffset += updateSize;
61 m_fileSize -= updateSize;
62
63 return QCommandApdu::build(0x00, QCommandApdu::UpdateBinary, fileOffset >> 8,
64 fileOffset & 0xFF, m_ndefData.mid(fileOffset - 2, updateSize));
65 }
66 case WriteNdefLength: {
68 qToUnaligned(qToBigEndian<uint16_t>(m_ndefData.size()), data.data());
69
70 return QCommandApdu::build(0x00, QCommandApdu::UpdateBinary, 0x00, 0x00, data);
71 }
72 default:
73 nextAction = Unexpected;
74 return {};
75 }
76}
77
79{
80 if (m_currentState == NdefMessageRead) {
81 auto message = QNdefMessage::fromByteArray(m_ndefData);
82 m_ndefData.clear();
83 m_currentState = NdefSupportDetected;
84 nextAction = Done;
85 return message;
86 }
87
88 nextAction = Unexpected;
89 return {};
90}
91
93{
94 switch (m_currentState) {
95 case SelectApplicationForProbe:
96 m_targetState = NdefSupportDetected;
97 return SendCommand;
98 case NdefSupportDetected:
99 return Done;
100 case NdefNotSupported:
101 return Failed;
102 default:
103 return Unexpected;
104 }
105}
106
108{
109 switch (m_currentState) {
110 case SelectApplicationForProbe:
111 m_targetState = NdefMessageRead;
112 return SendCommand;
113 case NdefSupportDetected:
114 m_currentState = SelectApplicationForRead;
115 m_targetState = NdefMessageRead;
116 return SendCommand;
117 case NdefNotSupported:
118 return Failed;
119 default:
120 return Unexpected;
121 }
122}
123
125{
126 // Only one message per tag is supported
127 if (messages.isEmpty() || messages.size() > 1)
128 return Failed;
129
130 auto messageData = messages.first().toByteArray();
131 if (messageData.size() > m_maxNdefSize - 2)
132 return Failed;
133
134 m_ndefData = messageData;
135
136 m_targetState = NdefMessageWritten;
137
138 switch (m_currentState) {
139 case SelectApplicationForProbe:
140 return SendCommand;
141
142 case NdefNotSupported:
143 return Failed;
144
145 case NdefSupportDetected:
146 if (!m_writable)
147 return Failed;
148
149 m_currentState = SelectApplicationForWrite;
150 return SendCommand;
151
152 default:
153 return Unexpected;
154 };
155}
156
158{
159 QResponseApdu apdu(response);
160
161 switch (m_currentState) {
162 case SelectApplicationForProbe:
163 return handleSimpleResponse(apdu, SelectCCFile, NdefNotSupported);
164 case SelectCCFile:
165 return handleSimpleResponse(apdu, ReadCCFile, NdefNotSupported);
166 case ReadCCFile:
167 return handleReadCCResponse(apdu);
168
169 case SelectApplicationForRead:
170 return handleSimpleResponse(apdu, SelectNdefFileForRead, NdefSupportDetected);
171 case SelectNdefFileForRead:
172 return handleSimpleResponse(apdu, ReadNdefMessageLength, NdefSupportDetected);
173 case ReadNdefMessageLength:
174 return handleReadFileLengthResponse(apdu);
175 case ReadNdefMessage:
176 return handleReadFileResponse(apdu);
177
178 case SelectApplicationForWrite:
179 return handleSimpleResponse(apdu, SelectNdefFileForWrite, NdefSupportDetected);
180 case SelectNdefFileForWrite:
181 return handleSimpleResponse(apdu, ClearNdefLength, NdefSupportDetected);
182 case ClearNdefLength:
183 return handleSimpleResponse(apdu, WriteNdefFile, NdefSupportDetected);
184 case WriteNdefFile:
185 return handleWriteNdefFileResponse(apdu);
186 case WriteNdefLength:
187 return handleSimpleResponse(apdu, NdefSupportDetected, NdefSupportDetected, Done);
188
189 default:
190 return Unexpected;
191 }
192}
193
194QNdefAccessFsm::Action QNfcTagType4NdefFsm::handleSimpleResponse(
195 const QResponseApdu &response, QNfcTagType4NdefFsm::State okState,
196 QNfcTagType4NdefFsm::State failedState, QNdefAccessFsm::Action okAction)
197{
198 if (!response.isOk()) {
199 m_currentState = failedState;
200 return Failed;
201 }
202
203 m_currentState = okState;
204 return okAction;
205}
206
207QNdefAccessFsm::Action QNfcTagType4NdefFsm::handleReadCCResponse(const QResponseApdu &response)
208{
209 m_currentState = NdefNotSupported;
210
211 if (!response.isOk())
212 return Failed;
213
214 if (response.data().size() < 15) {
215 qCDebug(QT_NFC_T4T) << "Invalid response size";
216 return Failed;
217 }
218
219 qsizetype idx = 0;
220 auto readU8 = [&data = response.data(), &idx]() {
221 return static_cast<uint8_t>(data.at(idx++));
222 };
223 auto readU16 = [&data = response.data(), &idx]() {
224 Q_ASSERT(idx >= 0 && idx <= data.size() - 2);
225 uint16_t res = qFromBigEndian(qFromUnaligned<uint16_t>(data.constData() + idx));
226 idx += 2;
227 return res;
228 };
229 auto readBytes = [&data = response.data(), &idx](qsizetype count) {
230 auto res = data.sliced(idx, count);
231 idx += count;
232 return res;
233 };
234 auto ccLen = readU16();
235 if (ccLen < 15) {
236 qCDebug(QT_NFC_T4T) << "CC length is too small";
237 return Failed;
238 }
239 auto mapping = readU8();
240 if ((mapping & 0xF0) != 0x20) {
241 qCDebug(QT_NFC_T4T) << "Unsupported mapping:" << Qt::hex << mapping;
242 return Failed;
243 }
244 m_maxReadSize = readU16();
245 if (m_maxReadSize < 0xF) {
246 qCDebug(QT_NFC_T4T) << "Invalid maxReadSize" << m_maxReadSize;
247 return Failed;
248 }
249
250 m_maxUpdateSize = readU16();
251 auto tlvTag = readU8();
252 if (tlvTag != 0x04) {
253 qCDebug(QT_NFC_T4T) << "Invalid TLV tag";
254 return Failed;
255 }
256 auto tlvSize = readU8();
257 if (tlvSize == 0xFF || tlvSize < 6) {
258 qCDebug(QT_NFC_T4T) << "Invalid TLV size";
259 return Failed;
260 }
261 m_ndefFileId = readBytes(2);
262
263 m_maxNdefSize = readU16();
264 if (m_maxNdefSize < 2) {
265 qCDebug(QT_NFC_T4T) << "No space for NDEF file length";
266 return Failed;
267 }
268
269 /*
270 The specification defined value 0 for read access and write access
271 fields to mean that access is granted without any security, all
272 other values are either reserved, proprietary, or indicate than no
273 access is granted at all. Here all non-zero value are handled as
274 no access is granted.
275 */
276 auto readAccess = readU8();
277 if (readAccess != 0) {
278 qCDebug(QT_NFC_T4T) << "No read access";
279 return Failed;
280 }
281 auto writeAccess = readU8();
282 // It's not possible to atomically clear the length field if update
283 // size is < 2, so handle such tags as read-only. This also simplifies
284 // the update logic (no need to ever split ClearNdefLength/WriteNdefLength
285 // states)
286 m_writable = writeAccess == 0 && m_maxUpdateSize >= 2;
287
288 m_currentState = NdefSupportDetected;
289
290 if (m_targetState == NdefSupportDetected) {
291 return Done;
292 } else if (m_targetState == NdefMessageRead) {
293 // Skip extra application select
294 m_currentState = SelectNdefFileForRead;
295 return SendCommand;
296 } else if (m_targetState == NdefMessageWritten) {
297 if (m_writable) {
298 if (m_ndefData.size() > m_maxNdefSize - 2) {
299 qCDebug(QT_NFC_T4T) << "Message is too large";
300 return Failed;
301 }
302
303 // Skip extra application select
304 m_currentState = SelectNdefFileForWrite;
305 return SendCommand;
306 } else {
307 return Failed;
308 }
309 }
310
311 return Unexpected;
312}
313
315QNfcTagType4NdefFsm::handleReadFileLengthResponse(const QResponseApdu &response)
316{
317 if (!response.isOk() || response.data().size() < 2) {
318 m_currentState = NdefSupportDetected;
319 return Failed;
320 }
321
322 m_fileSize = qFromBigEndian(qFromUnaligned<uint16_t>(response.data().constData()));
323 if (m_fileSize > m_maxNdefSize - 2) {
324 m_currentState = NdefSupportDetected;
325 return Failed;
326 }
327
328 m_fileOffset = 2;
329 m_ndefData.clear();
330
331 if (m_fileSize == 0) {
332 m_currentState = NdefMessageRead;
333 return GetMessage;
334 }
335
336 m_currentState = ReadNdefMessage;
337 return SendCommand;
338}
339
340QNdefAccessFsm::Action QNfcTagType4NdefFsm::handleReadFileResponse(const QResponseApdu &response)
341{
342 if (!response.isOk() || response.data().size() == 0) {
343 m_currentState = NdefSupportDetected;
344 return Failed;
345 }
346
347 auto readSize = qMin<qsizetype>(m_fileSize, response.data().size());
348 m_ndefData.append(response.data().first(readSize));
349 m_fileOffset += readSize;
350 m_fileSize -= readSize;
351
352 if (m_fileSize == 0) {
353 m_currentState = NdefMessageRead;
354 return GetMessage;
355 }
356
357 return SendCommand;
358}
359
361QNfcTagType4NdefFsm::handleWriteNdefFileResponse(const QResponseApdu &response)
362{
363 if (!response.isOk()) {
364 m_currentState = NdefSupportDetected;
365 return Failed;
366 }
367
368 if (m_fileSize == 0)
369 m_currentState = WriteNdefLength;
370
371 return SendCommand;
372}
373
static constexpr QByteArrayView fromArray(const Byte(&data)[Size]) noexcept
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
void clear()
Clears the contents of the byte array and makes it null.
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
QByteArray first(qsizetype n) const &
Definition qbytearray.h:196
The QNdefMessage class provides an NFC NDEF message.
static Q_NFC_EXPORT QNdefMessage fromByteArray(const QByteArray &message)
Returns an NDEF message parsed from the contents of message.
Action provideResponse(const QByteArray &response) override
Action writeMessages(const QList< QNdefMessage > &messages) override
Action readMessages() override
QNdefMessage getMessage(Action &nextAction) override
Action detectNdefSupport() override
bool isOk() const
const QByteArray & data() const
constexpr uint8_t Select
QByteArray build(uint8_t cla, uint8_t ins, uint8_t p1, uint8_t p2, QByteArrayView data, uint16_t ne=0)
constexpr uint8_t ReadBinary
constexpr uint8_t UpdateBinary
Combined button and popup list for selecting options.
QTextStream & hex(QTextStream &stream)
Calls QTextStream::setIntegerBase(16) on stream and returns stream.
constexpr Initialization Uninitialized
constexpr T qFromBigEndian(T source)
Definition qendian.h:174
QT_BEGIN_NAMESPACE Q_ALWAYS_INLINE void qToUnaligned(const T src, void *dest)
Definition qendian.h:34
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLenum GLenum GLsizei count
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint GLsizei const GLchar * message
GLuint res
GLenum GLenum GLenum GLenum mapping
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
ptrdiff_t qsizetype
Definition qtypes.h:165