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

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