Source code
Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qndefmessage.h"
#include "qndefrecord_p.h"
QT_BEGIN_NAMESPACE
QT_IMPL_METATYPE_EXTERN(QNdefMessage)
/*!
\class QNdefMessage
\brief The QNdefMessage class provides an NFC NDEF message.
\ingroup connectivity-nfc
\inmodule QtNfc
\since Qt 5.2
A QNdefMessage is a collection of 0 or more QNdefRecords. QNdefMessage inherits from
QList<QNdefRecord> and therefore the standard QList functions can be used to manipulate the
NDEF records in the message.
NDEF messages can be parsed from a byte array conforming to the NFC Data Exchange Format
technical specification by using the fromByteArray() static function. Conversely QNdefMessages
can be converted into a byte array with the toByteArray() function.
*/
/*!
\fn QNdefMessage::QNdefMessage()
Constructs a new empty NDEF message.
*/
/*!
\fn QNdefMessage::QNdefMessage(const QNdefRecord &record)
Constructs a new NDEF message containing a single record \a record.
*/
/*!
\fn QNdefMessage::QNdefMessage(const QNdefMessage &message)
Constructs a new NDEF message that is a copy of \a message.
*/
/*!
\fn QNdefMessage::QNdefMessage(const QList<QNdefRecord> &records)
Constructs a new NDEF message that contains all of the records in \a records.
*/
/*!
Returns an NDEF message parsed from the contents of \a message.
The \a message parameter is interpreted as the raw message format defined in the NFC Data
Exchange Format technical specification.
If a parse error occurs an empty NDEF message is returned.
*/
QNdefMessage QNdefMessage::fromByteArray(const QByteArray &message)
{
QNdefMessage result;
bool seenMessageBegin = false;
bool seenMessageEnd = false;
// We need a special flag to detect chunking, because any payload of a chunk
// (including the first and the last) can be empty, so using
// partialChunk.size() is not enough.
bool chunkStarted = false;
QByteArray partialChunk;
QNdefRecord record;
qsizetype idx = 0;
while (idx < message.size()) {
quint8 flags = message.at(idx);
const bool messageBegin = flags & 0x80;
const bool messageEnd = flags & 0x40;
const bool cf = flags & 0x20;
const bool sr = flags & 0x10;
const bool il = flags & 0x08;
const quint8 typeNameFormat = flags & 0x07;
if (messageBegin && seenMessageBegin) {
qWarning("Got message begin but already parsed some records");
return QNdefMessage();
} else if (!messageBegin && !seenMessageBegin) {
qWarning("Haven't got message begin yet");
return QNdefMessage();
} else if (messageBegin && !seenMessageBegin) {
seenMessageBegin = true;
}
if (messageEnd && seenMessageEnd) {
qWarning("Got message end but already parsed final record");
return QNdefMessage();
} else if (messageEnd && !seenMessageEnd) {
seenMessageEnd = true;
}
// TNF must be 0x06 even for the last chunk, when cf == 0.
if ((typeNameFormat != 0x06) && chunkStarted) {
qWarning("Partial chunk not empty, but TNF not 0x06 as expected");
return QNdefMessage();
}
// TNF cannot be 0x06 if the chunking was not started
if ((typeNameFormat == 0x06) && !chunkStarted) {
qWarning("Unexpected TNF=0x06 when chunking was not started");
return QNdefMessage();
}
int headerLength = 1;
headerLength += (sr) ? 1 : 4;
headerLength += (il) ? 1 : 0;
if (idx + headerLength >= message.size()) {
qWarning("Unexpected end of message");
return QNdefMessage();
}
const quint8 typeLength = message.at(++idx);
if ((typeNameFormat == 0x06) && (typeLength != 0)) {
qWarning("Invalid chunked data, TYPE_LENGTH != 0");
return QNdefMessage();
}
quint32 payloadLength;
if (sr)
payloadLength = quint8(message.at(++idx));
else {
payloadLength = quint8(message.at(++idx)) << 24;
payloadLength |= quint8(message.at(++idx)) << 16;
payloadLength |= quint8(message.at(++idx)) << 8;
payloadLength |= quint8(message.at(++idx)) << 0;
}
quint8 idLength;
if (il)
idLength = message.at(++idx);
else
idLength = 0;
// On 32-bit systems this can overflow
const qsizetype convertedPayloadLength = static_cast<qsizetype>(payloadLength);
const qsizetype contentLength = convertedPayloadLength + typeLength + idLength;
// On a 32 bit platform the payload can theoretically exceed the max.
// size of a QByteArray. This will never happen in practice with correct
// data because there are no NFC tags that can store such data sizes,
// but still can be possible if the data is corrupted.
if ((contentLength < 0) || (convertedPayloadLength < 0)
|| ((std::numeric_limits<qsizetype>::max() - idx) < contentLength)) {
qWarning("Payload can't fit into QByteArray");
return QNdefMessage();
}
if (idx + contentLength >= message.size()) {
qWarning("Unexpected end of message");
return QNdefMessage();
}
if ((typeNameFormat == 0x06) && il) {
qWarning("Invalid chunked data, IL != 0");
return QNdefMessage();
}
if (typeNameFormat != 0x06)
record.setTypeNameFormat(QNdefRecord::TypeNameFormat(typeNameFormat));
if (typeLength > 0) {
QByteArray type(&message.constData()[++idx], typeLength);
record.setType(type);
idx += typeLength - 1;
}
if (idLength > 0) {
QByteArray id(&message.constData()[++idx], idLength);
record.setId(id);
idx += idLength - 1;
}
{
QByteArray payload;
if (payloadLength > 0) {
payload = QByteArray(&message.constData()[++idx], payloadLength);
idx += payloadLength - 1;
}
if (cf) {
// chunked payload, except last
chunkStarted = true;
partialChunk.append(payload);
} else if (typeNameFormat == 0x06) {
// last chunk of chunked payload
record.setPayload(partialChunk + payload);
partialChunk.clear();
chunkStarted = false;
} else {
// non-chunked payload
record.setPayload(payload);
}
}
if (!cf) {
result.append(record);
record = QNdefRecord();
}
if (!cf && seenMessageEnd)
break;
// move to start of next record
++idx;
}
if (!seenMessageBegin || !seenMessageEnd) {
qWarning("Malformed NDEF Message, missing begin or end");
return QNdefMessage();
}
return result;
}
/*!
\fn QNdefMessage &QNdefMessage::operator=(const QNdefMessage &other)
\overload
\since 6.2
Copy assignment operator from QList<QNdefRecord>. Assigns the
\a other list of NDEF records to this NDEF record list.
After the operation, \a other and \c *this will be equal.
*/
/*!
\fn QNdefMessage &QNdefMessage::operator=(QNdefMessage &&other)
\overload
\since 6.2
Move assignment operator from QList<QNdefRecord>. Moves the
\a other list of NDEF records to this NDEF record list.
After the operation, \a other will be empty.
*/
/*!
Returns \c true if this NDEF message is equivalent to \a other; otherwise
returns \c false.
An empty message (i.e. isEmpty() returns \c true) is equivalent to a NDEF
message containing a single record of type \l QNdefRecord::Empty.
*/
bool QNdefMessage::operator==(const QNdefMessage &other) const
{
// both records are empty
if (isEmpty() && other.isEmpty())
return true;
// compare empty to really empty
if (isEmpty() && other.size() == 1 && other.first().typeNameFormat() == QNdefRecord::Empty)
return true;
if (other.isEmpty() && size() == 1 && first().typeNameFormat() == QNdefRecord::Empty)
return true;
if (size() != other.size())
return false;
for (qsizetype i = 0; i < size(); ++i) {
if (at(i) != other.at(i))
return false;
}
return true;
}
/*!
Returns the NDEF message as a byte array.
The return value of this function conforms to the format defined in the NFC Data Exchange
Format technical specification.
*/
QByteArray QNdefMessage::toByteArray() const
{
// An empty message is treated as a message containing a single empty record.
if (isEmpty())
return QNdefMessage(QNdefRecord()).toByteArray();
QByteArray m;
for (qsizetype i = 0; i < size(); ++i) {
const QNdefRecord &record = at(i);
quint8 flags = record.typeNameFormat();
if (i == 0)
flags |= 0x80;
if (i == size() - 1)
flags |= 0x40;
// cf (chunked records) not supported yet
if (record.payload().size() < 255)
flags |= 0x10;
if (!record.id().isEmpty())
flags |= 0x08;
m.append(flags);
m.append(record.type().size());
if (flags & 0x10) {
m.append(quint8(record.payload().size()));
} else {
quint32 length = record.payload().size();
m.append(length >> 24);
m.append(length >> 16);
m.append(length >> 8);
m.append(length & 0x000000ff);
}
if (flags & 0x08)
m.append(record.id().size());
if (!record.type().isEmpty())
m.append(record.type());
if (!record.id().isEmpty())
m.append(record.id());
if (!record.payload().isEmpty())
m.append(record.payload());
}
return m;
}
QT_END_NAMESPACE