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 
43 QT_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 */
fromByteArray(const QByteArray & message)94 QNdefMessage 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("Got message begin but already parsed some records");
118             return QNdefMessage();
119         } else if (!messageBegin && !seenMessageBegin) {
120             qWarning("Haven't got message begin yet");
121             return QNdefMessage();
122         } else if (messageBegin && !seenMessageBegin) {
123             seenMessageBegin = true;
124         }
125         if (messageEnd && seenMessageEnd) {
126             qWarning("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("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("Unexpected end of message");
142             return QNdefMessage();
143         }
144 
145         quint8 typeLength = *(++i);
146 
147         if ((typeNameFormat == 0x06) && (typeLength != 0)) {
148             qWarning("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("Unexpected end of message");
171             return QNdefMessage();
172         }
173 
174         if ((typeNameFormat == 0x06) && (idLength != 0)) {
175             qWarning("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(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(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("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 */
operator ==(const QNdefMessage & other) const239 bool 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 */
toByteArray() const268 QByteArray 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(flags);
295         m.append(record.type().length());
296 
297         if (flags & 0x10) {
298             m.append(quint8(record.payload().length()));
299         } else {
300             quint32 length = record.payload().length();
301             m.append(length >> 24);
302             m.append(length >> 16);
303             m.append(length >> 8);
304             m.append(length & 0x000000ff);
305         }
306 
307         if (flags & 0x08)
308             m.append(record.id().length());
309 
310         if (!record.type().isEmpty())
311             m.append(record.type());
312 
313         if (!record.id().isEmpty())
314             m.append(record.id());
315 
316         if (!record.payload().isEmpty())
317             m.append(record.payload());
318     }
319 
320     return m;
321 }
322 
323 QT_END_NAMESPACE
324