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 QByteArray partialChunk;
69 QNdefRecord record;
70
71 qsizetype idx = 0;
72 while (idx < message.size()) {
73 quint8 flags = message.at(idx);
74
75 const bool messageBegin = flags & 0x80;
76 const bool messageEnd = flags & 0x40;
77
78 const bool cf = flags & 0x20;
79 const bool sr = flags & 0x10;
80 const bool il = flags & 0x08;
81 const quint8 typeNameFormat = flags & 0x07;
82
83 if (messageBegin && seenMessageBegin) {
84 qWarning("Got message begin but already parsed some records");
85 return QNdefMessage();
86 } else if (!messageBegin && !seenMessageBegin) {
87 qWarning("Haven't got message begin yet");
88 return QNdefMessage();
89 } else if (messageBegin && !seenMessageBegin) {
90 seenMessageBegin = true;
91 }
92 if (messageEnd && seenMessageEnd) {
93 qWarning("Got message end but already parsed final record");
94 return QNdefMessage();
95 } else if (messageEnd && !seenMessageEnd) {
96 seenMessageEnd = true;
97 }
98 // TNF must be 0x06 even for the last chunk, when cf == 0.
99 if ((typeNameFormat != 0x06) && !partialChunk.isEmpty()) {
100 qWarning("Partial chunk not empty, but TNF not 0x06 as expected");
101 return QNdefMessage();
102 }
103
104 int headerLength = 1;
105 headerLength += (sr) ? 1 : 4;
106 headerLength += (il) ? 1 : 0;
107
108 if (idx + headerLength >= message.size()) {
109 qWarning("Unexpected end of message");
110 return QNdefMessage();
111 }
112
113 const quint8 typeLength = message.at(++idx);
114
115 if ((typeNameFormat == 0x06) && (typeLength != 0)) {
116 qWarning("Invalid chunked data, TYPE_LENGTH != 0");
117 return QNdefMessage();
118 }
119
120 quint32 payloadLength;
121 if (sr)
122 payloadLength = quint8(message.at(++idx));
123 else {
124 payloadLength = quint8(message.at(++idx)) << 24;
125 payloadLength |= quint8(message.at(++idx)) << 16;
126 payloadLength |= quint8(message.at(++idx)) << 8;
127 payloadLength |= quint8(message.at(++idx)) << 0;
128 }
129
130 quint8 idLength;
131 if (il)
132 idLength = message.at(++idx);
133 else
134 idLength = 0;
135
136 // On 32-bit systems this can overflow
137 const qsizetype convertedPayloadLength = static_cast<qsizetype>(payloadLength);
138 const qsizetype contentLength = convertedPayloadLength + typeLength + idLength;
139
140 // On a 32 bit platform the payload can theoretically exceed the max.
141 // size of a QByteArray. This will never happen in practice with correct
142 // data because there are no NFC tags that can store such data sizes,
143 // but still can be possible if the data is corrupted.
144 if ((contentLength < 0) || (convertedPayloadLength < 0)
145 || ((std::numeric_limits<qsizetype>::max() - idx) < contentLength)) {
146 qWarning("Payload can't fit into QByteArray");
147 return QNdefMessage();
148 }
149
150 if (idx + contentLength >= message.size()) {
151 qWarning("Unexpected end of message");
152 return QNdefMessage();
153 }
154
155 if ((typeNameFormat == 0x06) && il) {
156 qWarning("Invalid chunked data, IL != 0");
157 return QNdefMessage();
158 }
159
160 if (typeNameFormat != 0x06)
161 record.setTypeNameFormat(QNdefRecord::TypeNameFormat(typeNameFormat));
162
163 if (typeLength > 0) {
164 QByteArray type(&message.constData()[++idx], typeLength);
165 record.setType(type);
166 idx += typeLength - 1;
167 }
168
169 if (idLength > 0) {
170 QByteArray id(&message.constData()[++idx], idLength);
171 record.setId(id);
172 idx += idLength - 1;
173 }
174
175 if (payloadLength > 0) {
176 QByteArray payload(&message.constData()[++idx], payloadLength);
177
178 if (cf) {
179 // chunked payload, except last
180 partialChunk.append(payload);
181 } else if (typeNameFormat == 0x06) {
182 // last chunk of chunked payload
183 record.setPayload(partialChunk + payload);
184 partialChunk.clear();
185 } else {
186 // non-chunked payload
187 record.setPayload(payload);
188 }
189
190 idx += payloadLength - 1;
191 }
192
193 if (!cf) {
194 result.append(record);
195 record = QNdefRecord();
196 }
197
198 if (!cf && seenMessageEnd)
199 break;
200
201 // move to start of next record
202 ++idx;
203 }
204
205 if (!seenMessageBegin || !seenMessageEnd) {
206 qWarning("Malformed NDEF Message, missing begin or end");
207 return QNdefMessage();
208 }
209
210 return result;
211}
212
213/*!
214 \fn QNdefMessage &QNdefMessage::operator=(const QNdefMessage &other)
215 \overload
216 \since 6.2
217
218 Copy assignment operator from QList<QNdefRecord>. Assigns the
219 \a other list of NDEF records to this NDEF record list.
220
221 After the operation, \a other and \c *this will be equal.
222*/
223
224/*!
225 \fn QNdefMessage &QNdefMessage::operator=(QNdefMessage &&other)
226 \overload
227 \since 6.2
228
229 Move assignment operator from QList<QNdefRecord>. Moves the
230 \a other list of NDEF records to this NDEF record list.
231
232 After the operation, \a other will be empty.
233*/
234
235/*!
236 Returns \c true if this NDEF message is equivalent to \a other; otherwise
237 returns \c false.
238
239 An empty message (i.e. isEmpty() returns \c true) is equivalent to a NDEF
240 message containing a single record of type \l QNdefRecord::Empty.
241*/
242bool QNdefMessage::operator==(const QNdefMessage &other) const
243{
244 // both records are empty
245 if (isEmpty() && other.isEmpty())
246 return true;
247
248 // compare empty to really empty
249 if (isEmpty() && other.size() == 1 && other.first().typeNameFormat() == QNdefRecord::Empty)
250 return true;
251 if (other.isEmpty() && size() == 1 && first().typeNameFormat() == QNdefRecord::Empty)
252 return true;
253
254 if (size() != other.size())
255 return false;
256
257 for (qsizetype i = 0; i < size(); ++i) {
258 if (at(i) != other.at(i))
259 return false;
260 }
261
262 return true;
263}
264
265/*!
266 Returns the NDEF message as a byte array.
267
268 The return value of this function conforms to the format defined in the NFC Data Exchange
269 Format technical specification.
270*/
271QByteArray QNdefMessage::toByteArray() const
272{
273 // An empty message is treated as a message containing a single empty record.
274 if (isEmpty())
275 return QNdefMessage(QNdefRecord()).toByteArray();
276
277 QByteArray m;
278
279 for (qsizetype i = 0; i < size(); ++i) {
280 const QNdefRecord &record = at(i);
281
282 quint8 flags = record.typeNameFormat();
283
284 if (i == 0)
285 flags |= 0x80;
286 if (i == size() - 1)
287 flags |= 0x40;
288
289 // cf (chunked records) not supported yet
290
291 if (record.payload().size() < 255)
292 flags |= 0x10;
293
294 if (!record.id().isEmpty())
295 flags |= 0x08;
296
297 m.append(flags);
298 m.append(record.type().size());
299
300 if (flags & 0x10) {
301 m.append(quint8(record.payload().size()));
302 } else {
303 quint32 length = record.payload().size();
304 m.append(length >> 24);
305 m.append(length >> 16);
306 m.append(length >> 8);
307 m.append(length & 0x000000ff);
308 }
309
310 if (flags & 0x08)
311 m.append(record.id().size());
312
313 if (!record.type().isEmpty())
314 m.append(record.type());
315
316 if (!record.id().isEmpty())
317 m.append(record.id());
318
319 if (!record.payload().isEmpty())
320 m.append(record.payload());
321 }
322
323 return m;
324}
325
326QT_END_NAMESPACE