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 | |
7 | QT_BEGIN_NAMESPACE |
8 | |
9 | QT_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 | */ |
60 | QNdefMessage 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 = 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 | */ |
241 | bool 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 | */ |
270 | QByteArray 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 | |
325 | QT_END_NAMESPACE |
326 | |