Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qndefmessage.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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// Qt-Security score:critical reason:data-parser
4
5#include "qndefmessage.h"
7
9
10QT_IMPL_METATYPE_EXTERN(QNdefMessage)
11
12/*!
13 \class QNdefMessage
14 \brief The QNdefMessage class provides an NFC NDEF message.
15
16 \ingroup connectivity-nfc
17 \inmodule QtNfc
18 \since Qt 5.2
19
20 A QNdefMessage is a collection of 0 or more QNdefRecords. QNdefMessage inherits from
21 QList<QNdefRecord> and therefore the standard QList functions can be used to manipulate the
22 NDEF records in the message.
23
24 NDEF messages can be parsed from a byte array conforming to the NFC Data Exchange Format
25 technical specification by using the fromByteArray() static function. Conversely QNdefMessages
26 can be converted into a byte array with the toByteArray() function.
27*/
28
29/*!
30 \fn QNdefMessage::QNdefMessage()
31
32 Constructs a new empty NDEF message.
33*/
34
35/*!
36 \fn QNdefMessage::QNdefMessage(const QNdefRecord &record)
37
38 Constructs a new NDEF message containing a single record \a record.
39*/
40
41/*!
42 \fn QNdefMessage::QNdefMessage(const QNdefMessage &message)
43
44 Constructs a new NDEF message that is a copy of \a message.
45*/
46
47/*!
48 \fn QNdefMessage::QNdefMessage(const QList<QNdefRecord> &records)
49
50 Constructs a new NDEF message that contains all of the records in \a records.
51*/
52
53/*!
54 Returns an NDEF message parsed from the contents of \a message.
55
56 The \a message parameter is interpreted as the raw message format defined in the NFC Data
57 Exchange Format technical specification.
58
59 If a parse error occurs an empty NDEF message is returned.
60*/
61QNdefMessage QNdefMessage::fromByteArray(const QByteArray &message)
62{
63 QNdefMessage result;
64
65 bool seenMessageBegin = false;
66 bool seenMessageEnd = false;
67
68 // We need a special flag to detect chunking, because any payload of a chunk
69 // (including the first and the last) can be empty, so using
70 // partialChunk.size() is not enough.
71 bool chunkStarted = false;
72 QByteArray partialChunk;
73 QNdefRecord record;
74
75 qsizetype idx = 0;
76 while (idx < message.size()) {
77 quint8 flags = message.at(idx);
78
79 const bool messageBegin = flags & 0x80;
80 const bool messageEnd = flags & 0x40;
81
82 const bool cf = flags & 0x20;
83 const bool sr = flags & 0x10;
84 const bool il = flags & 0x08;
85 const quint8 typeNameFormat = flags & 0x07;
86
87 if (messageBegin && seenMessageBegin) {
88 qWarning("Got message begin but already parsed some records");
89 return QNdefMessage();
90 } else if (!messageBegin && !seenMessageBegin) {
91 qWarning("Haven't got message begin yet");
92 return QNdefMessage();
93 } else if (messageBegin && !seenMessageBegin) {
94 seenMessageBegin = true;
95 }
96 if (messageEnd && seenMessageEnd) {
97 qWarning("Got message end but already parsed final record");
98 return QNdefMessage();
99 } else if (messageEnd && !seenMessageEnd) {
100 seenMessageEnd = true;
101 }
102 // TNF must be 0x06 even for the last chunk, when cf == 0.
103 if ((typeNameFormat != 0x06) && chunkStarted) {
104 qWarning("Partial chunk not empty, but TNF not 0x06 as expected");
105 return QNdefMessage();
106 }
107 // TNF cannot be 0x06 if the chunking was not started
108 if ((typeNameFormat == 0x06) && !chunkStarted) {
109 qWarning("Unexpected TNF=0x06 when chunking was not started");
110 return QNdefMessage();
111 }
112
113 int headerLength = 1;
114 headerLength += (sr) ? 1 : 4;
115 headerLength += (il) ? 1 : 0;
116
117 if (idx + headerLength >= message.size()) {
118 qWarning("Unexpected end of message");
119 return QNdefMessage();
120 }
121
122 const quint8 typeLength = message.at(++idx);
123
124 if ((typeNameFormat == 0x06) && (typeLength != 0)) {
125 qWarning("Invalid chunked data, TYPE_LENGTH != 0");
126 return QNdefMessage();
127 }
128
129 quint32 payloadLength;
130 if (sr)
131 payloadLength = quint8(message.at(++idx));
132 else {
133 payloadLength = quint8(message.at(++idx)) << 24;
134 payloadLength |= quint8(message.at(++idx)) << 16;
135 payloadLength |= quint8(message.at(++idx)) << 8;
136 payloadLength |= quint8(message.at(++idx)) << 0;
137 }
138
139 quint8 idLength;
140 if (il)
141 idLength = message.at(++idx);
142 else
143 idLength = 0;
144
145 // On 32-bit systems this can overflow
146 const qsizetype convertedPayloadLength = static_cast<qsizetype>(payloadLength);
147 const qsizetype contentLength = convertedPayloadLength + typeLength + idLength;
148
149 // On a 32 bit platform the payload can theoretically exceed the max.
150 // size of a QByteArray. This will never happen in practice with correct
151 // data because there are no NFC tags that can store such data sizes,
152 // but still can be possible if the data is corrupted.
153 if ((contentLength < 0) || (convertedPayloadLength < 0)
154 || ((std::numeric_limits<qsizetype>::max() - idx) < contentLength)) {
155 qWarning("Payload can't fit into QByteArray");
156 return QNdefMessage();
157 }
158
159 if (idx + contentLength >= message.size()) {
160 qWarning("Unexpected end of message");
161 return QNdefMessage();
162 }
163
164 if ((typeNameFormat == 0x06) && il) {
165 qWarning("Invalid chunked data, IL != 0");
166 return QNdefMessage();
167 }
168
169 if (typeNameFormat != 0x06)
170 record.setTypeNameFormat(QNdefRecord::TypeNameFormat(typeNameFormat));
171
172 if (typeLength > 0) {
173 QByteArray type(&message.constData()[++idx], typeLength);
174 record.setType(type);
175 idx += typeLength - 1;
176 }
177
178 if (idLength > 0) {
179 QByteArray id(&message.constData()[++idx], idLength);
180 record.setId(id);
181 idx += idLength - 1;
182 }
183
184 {
185 QByteArray payload;
186 if (payloadLength > 0) {
187 payload = QByteArray(&message.constData()[++idx], payloadLength);
188 idx += payloadLength - 1;
189 }
190
191 if (cf) {
192 // chunked payload, except last
193 chunkStarted = true;
194 partialChunk.append(payload);
195 } else if (typeNameFormat == 0x06) {
196 // last chunk of chunked payload
197 record.setPayload(partialChunk + payload);
198 partialChunk.clear();
199 chunkStarted = false;
200 } else {
201 // non-chunked payload
202 record.setPayload(payload);
203 }
204 }
205
206 if (!cf) {
207 result.append(record);
208 record = QNdefRecord();
209 }
210
211 if (!cf && seenMessageEnd)
212 break;
213
214 // move to start of next record
215 ++idx;
216 }
217
218 if (!seenMessageBegin || !seenMessageEnd) {
219 qWarning("Malformed NDEF Message, missing begin or end");
220 return QNdefMessage();
221 }
222
223 return result;
224}
225
226/*!
227 \fn QNdefMessage &QNdefMessage::operator=(const QNdefMessage &other)
228 \overload
229 \since 6.2
230
231 Copy assignment operator from QList<QNdefRecord>. Assigns the
232 \a other list of NDEF records to this NDEF record list.
233
234 After the operation, \a other and \c *this will be equal.
235*/
236
237/*!
238 \fn QNdefMessage &QNdefMessage::operator=(QNdefMessage &&other)
239 \overload
240 \since 6.2
241
242 Move assignment operator from QList<QNdefRecord>. Moves the
243 \a other list of NDEF records to this NDEF record list.
244
245 After the operation, \a other will be empty.
246*/
247
248/*!
249 Returns \c true if this NDEF message is equivalent to \a other; otherwise
250 returns \c false.
251
252 An empty message (i.e. isEmpty() returns \c true) is equivalent to a NDEF
253 message containing a single record of type \l QNdefRecord::Empty.
254*/
255bool QNdefMessage::operator==(const QNdefMessage &other) const
256{
257 // both records are empty
258 if (isEmpty() && other.isEmpty())
259 return true;
260
261 // compare empty to really empty
262 if (isEmpty() && other.size() == 1 && other.first().typeNameFormat() == QNdefRecord::Empty)
263 return true;
264 if (other.isEmpty() && size() == 1 && first().typeNameFormat() == QNdefRecord::Empty)
265 return true;
266
267 if (size() != other.size())
268 return false;
269
270 for (qsizetype i = 0; i < size(); ++i) {
271 if (at(i) != other.at(i))
272 return false;
273 }
274
275 return true;
276}
277
278/*!
279 Returns the NDEF message as a byte array.
280
281 The return value of this function conforms to the format defined in the NFC Data Exchange
282 Format technical specification.
283*/
284QByteArray QNdefMessage::toByteArray() const
285{
286 // An empty message is treated as a message containing a single empty record.
287 if (isEmpty())
288 return QNdefMessage(QNdefRecord()).toByteArray();
289
290 QByteArray m;
291
292 for (qsizetype i = 0; i < size(); ++i) {
293 const QNdefRecord &record = at(i);
294
295 quint8 flags = record.typeNameFormat();
296
297 if (i == 0)
298 flags |= 0x80;
299 if (i == size() - 1)
300 flags |= 0x40;
301
302 // cf (chunked records) not supported yet
303
304 if (record.payload().size() < 255)
305 flags |= 0x10;
306
307 if (!record.id().isEmpty())
308 flags |= 0x08;
309
310 m.append(flags);
311 m.append(record.type().size());
312
313 if (flags & 0x10) {
314 m.append(quint8(record.payload().size()));
315 } else {
316 quint32 length = record.payload().size();
317 m.append(length >> 24);
318 m.append(length >> 16);
319 m.append(length >> 8);
320 m.append(length & 0x000000ff);
321 }
322
323 if (flags & 0x08)
324 m.append(record.id().size());
325
326 if (!record.type().isEmpty())
327 m.append(record.type());
328
329 if (!record.id().isEmpty())
330 m.append(record.id());
331
332 if (!record.payload().isEmpty())
333 m.append(record.payload());
334 }
335
336 return m;
337}
338
339QT_END_NAMESPACE
Combined button and popup list for selecting options.