// 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 #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 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 &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; 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) && !partialChunk.isEmpty()) { qWarning("Partial chunk not empty, but TNF not 0x06 as expected"); 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(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::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; } if (payloadLength > 0) { QByteArray payload(&message.constData()[++idx], payloadLength); if (cf) { // chunked payload, except last partialChunk.append(payload); } else if (typeNameFormat == 0x06) { // last chunk of chunked payload record.setPayload(partialChunk + payload); partialChunk.clear(); } else { // non-chunked payload record.setPayload(payload); } idx += payloadLength - 1; } 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. 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. 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