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

source code of qtconnectivity/src/nfc/qndefmessage.cpp