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 | */ |
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(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 = 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 | */ |
239 | 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 | */ |
268 | 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(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 | |
323 | QT_END_NAMESPACE |
324 | |