Revision control
++ // Copyright (C) 2017 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:significant reason:default
++
++ #include "qmodbuspdu.h"
++ #include "qmodbus_symbols_p.h"
++
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qhash.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using ReqSizeCalc = QHash<quint8, QModbusRequest::CalcFuncPtr>;
++ Q_GLOBAL_STATIC(ReqSizeCalc, requestSizeCalculators);
++
++ using ResSizeCalc = QHash<quint8, QModbusResponse::CalcFuncPtr>;
++ Q_GLOBAL_STATIC(ResSizeCalc, responseSizeCalculators);
++
++ struct QModbusPduPrivate
++ {
++ QModbusPduPrivate() = delete;
++ Q_DISABLE_COPY_MOVE(QModbusPduPrivate)
++
++ enum struct Type {
++ Request,
++ Response
++ };
++
++ /*!
++ \internal
++
++ Returns the minimum data size in bytes for the given \a pdu and the
++ Modbus PDU \a type. If the PDU's function code is invalid, undefined
++ or unknown, the return value will be \c {-1}.
++ */
++ static int minimumDataSize(const QModbusPdu &pdu, Type type)
++ {
++ if (pdu.isException())
++ return 1;
++
++ switch (pdu.functionCode()) {
++ case QModbusPdu::ReadCoils:
++ case QModbusPdu::ReadDiscreteInputs:
++ return type == Type::Request ? 4 : 2;
++ case QModbusPdu::WriteSingleCoil:
++ case QModbusPdu::WriteSingleRegister:
++ return 4;
++ case QModbusPdu::ReadHoldingRegisters:
++ case QModbusPdu::ReadInputRegisters:
++ return type == Type::Request ? 4 : 3;
++ case QModbusPdu::ReadExceptionStatus:
++ return type == Type::Request ? 0 : 1;
++ case QModbusPdu::Diagnostics:
++ return 4;
++ case QModbusPdu::GetCommEventCounter:
++ return type == Type::Request ? 0 : 4;
++ case QModbusPdu::GetCommEventLog:
++ return type == Type::Request ? 0 : 8;
++ case QModbusPdu::WriteMultipleCoils:
++ return type == Type::Request ? 6 : 4;
++ case QModbusPdu::WriteMultipleRegisters:
++ return type == Type::Request ? 7 : 4;
++ case QModbusPdu::ReportServerId:
++ return type == Type::Request ? 0 : 3;
++ case QModbusPdu::ReadFileRecord:
++ return type == Type::Request ? 8 : 5;
++ case QModbusPdu::WriteFileRecord:
++ return 10;
++ case QModbusPdu::MaskWriteRegister:
++ return 6;
++ case QModbusPdu::ReadWriteMultipleRegisters:
++ return type == Type::Request ? 11 : 3;
++ case QModbusPdu::ReadFifoQueue:
++ return type == Type::Request ? 2 : 6;
++ case QModbusPdu::EncapsulatedInterfaceTransport:
++ return 2;
++ case QModbusPdu::Invalid:
++ case QModbusPdu::UndefinedFunctionCode:
++ return -1;
++ }
++ return -1;
++ }
++
++ /*!
++ \internal
++
++ Extracts a Modbus PDU from a \a stream into the given \a pdu based on \a type.
++ */
++ static QDataStream &pduFromStream(QDataStream &stream, Type type, QModbusPdu *pdu)
++ {
++ struct RAII {
++ RAII(QModbusPdu *ptr = nullptr)
++ : tmp(ptr) {}
++ QModbusPdu *tmp{ nullptr };
++ ~RAII() { if (tmp) *tmp = {}; }
++ } raii = { pdu };
++
++ QModbusPdu::FunctionCode code = QModbusPdu::FunctionCode::Invalid;
++ stream >> code;
++ if (stream.status() == QDataStream::ReadPastEnd)
++ return stream;
++ pdu->setFunctionCode(code);
++
++ if (code == QModbusPdu::Invalid) // shortcut
++ return stream;
++
++ constexpr const int MaxPduDataSize = 252; // in bytes
++
++ // The calculateDataSize(...) function might need some data inside the
++ // given PDU argument to be able to figure out the right data size (e.g.
++ // WriteMultipleCoils contains some kind of "header"). So fake fill the PDU
++ // with the maximum available data but no more than the allowed max PDU
++ // data size.
++ QByteArray data(MaxPduDataSize, Qt::Uninitialized);
++ int read = stream.device()->peek(data.data(), MaxPduDataSize);
++ if (read < 0)
++ return stream;
++
++ data.resize(read);
++ pdu->setData(data);
++
++ const bool isResponse = (type == Type::Response);
++ int size = isResponse ? QModbusResponse::calculateDataSize(*pdu)
++ : QModbusRequest::calculateDataSize(*pdu);
++
++ if (isResponse && (code == QModbusPdu::EncapsulatedInterfaceTransport)) {
++ quint8 meiType = 0;
++ pdu->decodeData(&meiType);
++ if (meiType == EncapsulatedInterfaceTransport::ReadDeviceIdentification) {
++ int left = size, offset = 0;
++ while ((left > 0) && (size <= MaxPduDataSize)) {
++ data.resize(size);
++ const int read = stream.readRawData(data.data() + offset, size - offset);
++ if ((read < 0) || (read != (size - offset))) {
++ size = 255; // bogus size
++ stream.setStatus(QDataStream::ReadCorruptData);
++ break; // error reading, bail, reset further down
++ }
++ offset += read;
++ left = QModbusResponse::calculateDataSize(QModbusResponse(code, data)) - offset;
++ size += left;
++ }
++ if ((stream.status() == QDataStream::Ok) && (size <= MaxPduDataSize)) {
++ raii = {};
++ pdu->setData(data);
++ return stream; // early return to avoid second read
++ }
++ } else {
++ data.resize(int(stream.device()->size() - 1)); // One byte for the function code.
++ }
++ } else if (pdu->functionCode() == QModbusPdu::Diagnostics) {
++ quint16 subCode = 0xffff;
++ pdu->decodeData(&subCode);
++ if (subCode == Diagnostics::ReturnQueryData)
++ data.resize(int(stream.device()->size() - 1)); // One byte for the function code.
++ }
++
++ if (data.size() <= MaxPduDataSize) {
++ data.resize(size);
++ if (stream.readRawData(data.data(), data.size()) == size) {
++ raii = {};
++ pdu->setData(data);
++ }
++ }
++ return stream;
++ }
++ };
++
++ /*!
++ \class QModbusPdu
++ \inmodule QtSerialBus
++ \since 5.8
++
++ \brief QModbusPdu is a abstract container class containing the function code and
++ payload that is stored inside a Modbus ADU.
++
++ The class provides access to the raw Modbus protocol packets as defined by
++ the Modbus Application Protocol Specification 1.1b.
++ */
++
++ /*!
++ \enum QModbusPdu::ExceptionCode
++
++ This enum describes all the possible error conditions as defined by Modbus Exception Codes.
++ They are set by the server after checking the appropriate error conditions in the reply to a
++ request and must be decoded by the client to operate on the exception code.
++
++ \value IllegalFunction Function code is not supported by device.
++ \value IllegalDataAddress The received data address in the query is not an
++ allowable address for the Modbus server.
++ \value IllegalDataValue The contained value in the request data field is
++ not an allowable value for the Modbus server.
++ \value ServerDeviceFailure An irrecoverable error occurred while the server
++ was attempting to perform the requested action.
++ \value Acknowledge Specialized use in conjunction with programming
++ commands.
++ \value ServerDeviceBusy The server is engaged in processing a long duration
++ program command.
++ \value NegativeAcknowledge The server cannot perform the program function
++ received in the query. This code is returned for an
++ unsuccessful programming request. The client should
++ request diagnostic or error information from the
++ server.
++ \value MemoryParityError Indicates that the extended file area failed to
++ pass a consistency check. Used in conjunction with
++ function codes 20 and 21. The exception code does
++ not refer to any parity settings of the
++ transmission line but only to the servers' internal
++ memory of file records.
++ \value GatewayPathUnavailable Indicates that the gateway was unable to allocate
++ an internal communication path from the input port
++ to the output port for processing the request.
++ \value GatewayTargetDeviceFailedToRespond Indicates that no response was obtained from the
++ target device behind a gateway.
++ Usually this means the target device is not online
++ on the network.
++ \value ExtendedException This is an extended exception as per Modbus
++ specification. Generally this code is used to
++ describe an exception that is otherwise further
++ described.
++ */
++
++ /*!
++ \enum QModbusPdu::FunctionCode
++
++ Defines the function code and the implicit type of action required by the server. Not all
++ Modbus devices can handle the same set of function codes.
++
++ \value Invalid Set by the default constructor, do not use.
++ \value ReadCoils Requests the status of one or more coils from a device.
++ \value ReadDiscreteInputs Requests the status of one or more input registers from
++ a device.
++ \value ReadHoldingRegisters Requests the status of one or more holding register
++ values from a device.
++ \value ReadInputRegisters Requests the status of one or more input register
++ values from a device.
++ \value WriteSingleCoil Requests to write a single coil on a device.
++ \value WriteSingleRegister Requests to write a single holding register on a device.
++ \value ReadExceptionStatus Requests the status of the eight Exception Status
++ outputs on a device.
++ \value Diagnostics Used to provide a series of tests for checking the
++ client server communication system, or checking internal
++ \value GetCommEventCounter Requests a status word and an event count from the
++ device's communication event counter.
++ \value GetCommEventLog Requests a status word, event count, message count,
++ and a field of event bytes from a device.
++ \value WriteMultipleCoils Requests to write one or more coils on a device.
++ \value WriteMultipleRegisters Requests to write one or more holding registers on a
++ device.
++ \value ReportServerId Requests the description of the type, the current
++ status, and other information specific to a device.
++ \value ReadFileRecord Requests a file record read.
++ \value WriteFileRecord Requests a file record write.
++ \value MaskWriteRegister Requests to modify the contents of a specified holding
++ register using a combination of an AND or OR mask, and
++ the register's current contents.
++ \value ReadWriteMultipleRegisters Requests the status of one or more holding register and
++ at the same time to write one or more holding registers
++ on a device.
++ \value ReadFifoQueue Requests to read the contents of a First-In-First-Out
++ (FIFO) queue of register in a remote device.
++ \value EncapsulatedInterfaceTransport Please refer to Annex A of the Modbus specification.
++ \value UndefinedFunctionCode Do not use.
++ */
++
++ /*!
++ \fn QModbusPdu::QModbusPdu()
++
++ Constructs an invalid QModbusPdu.
++ */
++
++ /*!
++ \fn QModbusPdu::~QModbusPdu()
++
++ Destroys a QModbusPdu.
++ */
++ QModbusPdu::~QModbusPdu()
++ = default;
++
++ /*!
++ \fn QModbusPdu::QModbusPdu(const QModbusPdu &other)
++
++ Constructs a QModbusPdu that is a copy of \a other.
++
++ */
++
++ /*!
++ \fn QModbusPdu &QModbusPdu::operator=(const QModbusPdu &other)
++
++ Makes a copy of the \a other and assigns it to this QModbusPdu object.
++ */
++
++ /*!
++ \fn QModbusPdu::QModbusPdu(FunctionCode code, const QByteArray &data)
++
++ Constructs a QModbusPdu with function code set to \a code and payload set to \a data.
++ The data is expected to be stored in big-endian byte order already.
++ */
++
++ /*!
++ \internal
++ \fn template <typename ... Args> QModbusPdu::QModbusPdu(FunctionCode code, Args ... data)
++
++ Constructs a QModbusPdu with function code set to \a code and payload set to \a data.
++ The data is converted and stored in big-endian byte order.
++
++ \note Usage is limited \c quint8 and \c quint16 only. This is because
++ \c QDataStream stream operators will not only append raw data, but also
++ e.g. size, count, etc. for complex types.
++ */
++
++ /*!
++ \fn bool QModbusPdu::isValid() const
++
++ Returns true if the PDU is valid; otherwise false.
++
++ A PDU is considered valid if the message code is in the range of 1 to 255 decimal and the
++ PDU's compound size (function code + data) does not exceed 253 bytes. A default constructed
++ PDU is invalid.
++ */
++
++ /*!
++ \variable QModbusPdu::ExceptionByte
++
++ The variable is initialized to 0x80.
++
++ Exceptions are reported in a defined packet format. A function code
++ is returned to the requesting client equal to the original function code,
++ except with its most significant bit set. This is equivalent to adding 0x80
++ to the value of the original function code.
++
++ This field may be used to mask the exception bit in the function field
++ of a raw Modbus packet.
++ */
++
++ /*!
++ \fn bool QModbusPdu::isException() const
++
++ Returns true if the PDU contains an exception code; otherwise false.
++ */
++
++ /*!
++ \fn QModbusPdu::ExceptionCode QModbusPdu::exceptionCode() const
++
++ Returns the response's exception code.
++ */
++
++ /*!
++ \fn qint16 QModbusPdu::size() const
++
++ Returns the PDU's full size, including function code and data size.
++ */
++
++ /*!
++ \fn qint16 QModbusPdu::dataSize() const
++
++ Returns the PDU's data size, excluding the function code.
++ */
++
++ /*!
++ \fn FunctionCode QModbusPdu::functionCode() const
++
++ Returns the PDU's function code.
++ */
++
++ /*!
++ \fn void QModbusPdu::setFunctionCode(FunctionCode code)
++
++ Sets the PDU's function code to \a code.
++ */
++
++ /*!
++ \fn QByteArray QModbusPdu::data() const
++
++ Returns the PDU's payload, excluding the function code.
++ The payload is stored in big-endian byte order.
++ */
++
++ /*!
++ \fn void QModbusPdu::setData(const QByteArray &data)
++
++ Sets the PDU's function payload to \a data. The data is expected to be stored in big-endian
++ byte order already.
++ */
++
++ /*!
++ \fn template <typename ... Args> void QModbusPdu::decodeData(Args && ... data) const
++
++ Converts the payload into host endianness and reads it into \a data. Data can be a variable
++ length argument list.
++
++ \code
++ QModbusResponsePdu response(QModbusPdu::ReportServerId);
++ response.encodeData(quint8(0x02), quint8(0x01), quint8(0xff));
++ quint8 count, id, run;
++ response.decodeData(&count, &id, &run);
++ \endcode
++
++ \note Usage is limited \c quint8 and \c quint16 only. This is because
++ \c QDataStream stream operators will not only append raw data, but also
++ e.g. size, count, etc. for complex types.
++ */
++
++ /*!
++ \fn template <typename ... Args> void QModbusPdu::encodeData(Args ... data)
++
++ Sets the payload to \a data. The data is converted and stored in big-endian byte order.
++
++ \code
++ QModbusRequestPdu request(QModbusPdu::ReadCoils);
++ // starting address and quantity of coils
++ request.encodeData(quint16(0x0c), quint16(0x0a));
++ \endcode
++
++ \note Usage is limited \c quint8 and \c quint16 only. This is because
++ \c QDataStream stream operators will not only append raw data, but also
++ e.g. size, count, etc. for complex types.
++ */
++
++ /*!
++ \relates QModbusPdu
++
++ Writes the Modbus \a pdu to the \a debug stream.
++ */
++ QDebug operator<<(QDebug debug, const QModbusPdu &pdu)
++ {
++ QDebugStateSaver _(debug);
++ debug.nospace().noquote() << "0x" << Qt::hex << qSetFieldWidth(2) << qSetPadChar(u'0')
++ << (pdu.isException() ? pdu.functionCode() | QModbusPdu::ExceptionByte : pdu.functionCode())
++ << qSetFieldWidth(0) << pdu.data().toHex();
++ return debug;
++ }
++
++ /*!
++ \relates QModbusPdu
++
++ Writes a \a pdu to the \a stream and returns a reference to the stream.
++ */
++ QDataStream &operator<<(QDataStream &stream, const QModbusPdu &pdu)
++ {
++ if (pdu.isException())
++ stream << static_cast<quint8> (pdu.functionCode() | QModbusPdu::ExceptionByte);
++ else
++ stream << static_cast<quint8> (pdu.functionCode());
++ if (!pdu.data().isEmpty())
++ stream.writeRawData(pdu.data().constData(), pdu.data().size());
++
++ return stream;
++ }
++
++ /*!
++ \class QModbusRequest
++ \inmodule QtSerialBus
++ \since 5.8
++
++ \brief QModbusRequest is a container class containing the function code and payload that is
++ stored inside a Modbus ADU.
++
++ A Modbus request usually consists of a single byte describing the \c FunctionCode and N bytes
++ of payload
++
++ A typical Modbus request can looks like this:
++ \code
++ QModbusRequest request(QModbusRequest::WriteMultipleCoils,
++ QByteArray::fromHex("0013000a02cd01"));
++ \endcode
++ \note When using the constructor taking the \c QByteArray, please make sure to convert the
++ containing data to big-endian byte order before creating the request.
++
++ The same request can be created like this, if the values are known at compile time:
++ \code
++ quint16 startAddress = 19, numberOfCoils = 10;
++ quint8 payloadInBytes = 2, outputHigh = 0xcd, outputLow = 0x01;
++ QModbusRequest request(QModbusRequest::WriteMultipleCoils, startAddress, numberOfCoils,
++ payloadInBytes, outputHigh, outputLow);
++ \endcode
++ */
++
++ /*!
++ \typedef QModbusRequest::CalcFuncPtr
++
++ Typedef for a pointer to a custom calculator function with the same signature as
++ \l QModbusRequest::calculateDataSize.
++ */
++
++ /*!
++ \fn QModbusRequest::QModbusRequest()
++
++ Constructs an invalid QModbusRequest.
++ */
++
++ /*!
++ \fn QModbusRequest::QModbusRequest(const QModbusPdu &pdu)
++
++ Constructs a copy of \a pdu.
++ */
++
++ /*!
++ \fn template <typename ... Args> QModbusRequest::QModbusRequest(FunctionCode code, Args... data)
++
++ Constructs a QModbusRequest with function code set to \a code and payload set to \a data.
++ The data is converted and stored in big-endian byte order.
++
++ \note Usage is limited \c quint8 and \c quint16 only. This is because
++ \c QDataStream stream operators will not only append raw data, but also
++ e.g. size, count, etc. for complex types.
++ */
++
++ /*!
++ \fn QModbusRequest::QModbusRequest(FunctionCode code, const QByteArray &data = QByteArray())
++
++ Constructs a QModbusResponse with function code set to \a code and payload set to \a data.
++ The data is expected to be stored in big-endian byte order already.
++ */
++
++ /*!
++ \internal
++ */
++ QModbusRequest::~QModbusRequest()
++ = default;
++
++ /*!
++ Returns the expected minimum data size for \a request based on the
++ request's function code; \c {-1} if the function code is not known.
++ */
++ int QModbusRequest::minimumDataSize(const QModbusRequest &request)
++ {
++ return QModbusPduPrivate::minimumDataSize(request, QModbusPduPrivate::Type::Request);
++ }
++
++ /*!
++ Calculates the expected data size for \a request based on the request's
++ function code and data. Returns the full size of the request's data part;
++ \c {-1} if the size could not be properly calculated.
++
++ \sa minimumDataSize
++ \sa registerDataSizeCalculator
++ */
++ int QModbusRequest::calculateDataSize(const QModbusRequest &request)
++ {
++ if (requestSizeCalculators.exists()) {
++ if (auto ptr = requestSizeCalculators()->value(quint8(request.functionCode()), nullptr))
++ return ptr(request);
++ }
++
++ if (request.isException())
++ return 1;
++
++ int size = -1;
++ int minimum = QModbusPduPrivate::minimumDataSize(request, QModbusPduPrivate::Type::Request);
++ if (minimum < 0)
++ return size;
++
++ switch (request.functionCode()) {
++ case QModbusPdu::WriteMultipleCoils:
++ minimum -= 1; // first payload payload byte
++ if (request.dataSize() >= minimum)
++ size = minimum + quint8(request.data().at(minimum - 1)) /*byte count*/;
++ break;
++ case QModbusPdu::WriteMultipleRegisters:
++ case QModbusPdu::ReadWriteMultipleRegisters:
++ minimum -= 2; // first 2 payload payload bytes
++ if (request.dataSize() >= minimum)
++ size = minimum + quint8(request.data().at(minimum - 1)) /*byte count*/;
++ break;
++ case QModbusPdu::ReadFileRecord:
++ case QModbusPdu::WriteFileRecord:
++ if (request.dataSize() >= 1)
++ size = 1 /*byte count*/ + quint8(request.data().at(0)) /*actual bytes*/;
++ break;
++ case QModbusPdu::EncapsulatedInterfaceTransport: {
++ if (request.dataSize() < minimum)
++ break; // can't calculate, let's return -1 to indicate error
++ quint8 meiType = 0;
++ request.decodeData(&meiType);
++ // ReadDeviceIdentification -> 3 == MEI type + Read device ID + Object Id
++ size = (meiType == EncapsulatedInterfaceTransport::ReadDeviceIdentification) ? 3 : minimum;
++ } break;
++ default:
++ size = minimum;
++ break;
++ }
++ return size;
++ }
++
++ /*!
++ This function registers a user-defined implementation to calculate the
++ request data size for function code \a fc. It can be used to extend or
++ override the implementation inside \l QModbusRequest::calculateDataSize().
++
++ The \c CalcFuncPtr is a typedef for a pointer to a custom \a calculator
++ function with the following signature:
++ \code
++ int myCalculateDataSize(const QModbusRequest &pdu);
++ \endcode
++ */
++ void QModbusRequest::registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr calculator)
++ {
++ requestSizeCalculators()->insert(quint8(fc), calculator);
++ }
++
++ /*!
++ \internal
++
++ Reads a FunctionCode from a \a stream.
++ In stream we serialize FunctionCode as one byte, so we use a temporary char
++ variable to make the code work on both little endian and big endian systems.
++ If reading from stream fails, code will retain original value.
++ */
++ QDataStream &operator>>(QDataStream &stream, QModbusPdu::FunctionCode &code)
++ {
++ char buffer;
++ if (stream.readRawData(&buffer, 1) == 1)
++ code = static_cast<QModbusPdu::FunctionCode>(buffer);
++ return stream;
++ }
++
++ /*!
++ \relates QModbusRequest
++
++ Reads a \a pdu from the \a stream and returns a reference to the stream.
++
++ \note The function might fail to properly stream PDU's with function code
++ \l QModbusPdu::Diagnostics or \l QModbusPdu::EncapsulatedInterfaceTransport
++ because of the missing size indicator inside the PDU. In particular this may
++ happen when the PDU is embedded into a stream that doesn't end with the
++ diagnostic/encapsulated request itself.
++ */
++ QDataStream &operator>>(QDataStream &stream, QModbusRequest &pdu)
++ {
++ return QModbusPduPrivate::pduFromStream(stream, QModbusPduPrivate::Type::Request, &pdu);
++ }
++
++ /*!
++ \class QModbusResponse
++ \inmodule QtSerialBus
++ \since 5.8
++
++ \brief QModbusResponse is a container class containing the function code and payload that is
++ stored inside a Modbus ADU.
++
++ A typical Modbus response can looks like this:
++ \code
++ QModbusResponse response(QModbusResponse::ReadCoils, QByteArray::fromHex("02cd01"));
++ \endcode
++ \note When using the constructor taking the \c QByteArray, please make sure to convert the
++ containing data to big-endian byte order before creating the request.
++
++ The same response can be created like this, if the values are known at compile time:
++ \code
++ quint8 payloadInBytes = 2, outputHigh = 0xcd, outputLow = 0x01;
++ QModbusResponse response(QModbusResponse::ReadCoils, payloadInBytes, outputHigh, outputLow);
++ \endcode
++ */
++
++
++ /*!
++ \typedef QModbusResponse::CalcFuncPtr
++
++ Typedef for a pointer to a custom calculator function with the same signature as
++ \l QModbusResponse::calculateDataSize.
++ */
++
++ /*!
++ \fn QModbusResponse::QModbusResponse()
++
++ Constructs an invalid QModbusResponse.
++ */
++
++ /*!
++ \fn QModbusResponse::QModbusResponse(const QModbusPdu &pdu)
++
++ Constructs a copy of \a pdu.
++ */
++
++ /*!
++ \fn template <typename ... Args> QModbusResponse::QModbusResponse(FunctionCode code, Args... data)
++
++ Constructs a QModbusResponse with function code set to \a code and payload set to \a data.
++ The data is converted and stored in big-endian byte order.
++
++ \note Usage is limited \c quint8 and \c quint16 only. This is because
++ \c QDataStream stream operators will not only append raw data, but also
++ e.g. size, count, etc. for complex types.
++ */
++
++ /*!
++ \fn QModbusResponse::QModbusResponse(FunctionCode code, const QByteArray &data = QByteArray())
++
++ Constructs a QModbusResponse with function code set to \a code and payload set to \a data.
++ The data is expected to be stored in big-endian byte order already.
++ */
++
++ /*!
++ \internal
++ */
++ QModbusResponse::~QModbusResponse()
++ = default;
++
++ /*!
++ Returns the expected minimum data size for \a response based on the
++ response's function code; \c {-1} if the function code is not known.
++ */
++ int QModbusResponse::minimumDataSize(const QModbusResponse &response)
++ {
++ return QModbusPduPrivate::minimumDataSize(response, QModbusPduPrivate::Type::Response);
++ }
++
++ /*!
++ Calculates the expected data size for \a response, based on the response's
++ function code and data. Returns the full size of the response's data part;
++ \c {-1} if the size could not be properly calculated.
++
++ \sa minimumDataSize
++ \sa registerDataSizeCalculator
++ */
++ int QModbusResponse::calculateDataSize(const QModbusResponse &response)
++ {
++ if (responseSizeCalculators.exists()) {
++ if (auto ptr = responseSizeCalculators()->value(quint8(response.functionCode()), nullptr))
++ return ptr(response);
++ }
++
++ if (response.isException())
++ return 1;
++
++ int size = -1;
++ int minimum = QModbusPduPrivate::minimumDataSize(response, QModbusPduPrivate::Type::Response);
++ if (minimum < 0)
++ return size;
++
++ switch (response.functionCode()) {
++ case QModbusResponse::ReadCoils:
++ case QModbusResponse::ReadDiscreteInputs:
++ case QModbusResponse::ReadHoldingRegisters:
++ case QModbusResponse::ReadInputRegisters:
++ case QModbusResponse::GetCommEventLog:
++ case QModbusResponse::ReadFileRecord:
++ case QModbusResponse::WriteFileRecord:
++ case QModbusResponse::ReadWriteMultipleRegisters:
++ case QModbusResponse::ReportServerId:
++ if (response.dataSize() >= 1)
++ size = 1 /*byte count*/ + quint8(response.data().at(0)) /*actual bytes*/;
++ break;
++ case QModbusResponse::ReadFifoQueue: {
++ if (response.dataSize() >= 2) {
++ quint16 rawSize = 0;
++ response.decodeData(&rawSize);
++ size = rawSize + 2; // 2 bytes size info
++ }
++ } break;
++ case QModbusPdu::EncapsulatedInterfaceTransport: {
++ if (response.dataSize() < minimum)
++ break; // can't calculate, let's return -1 to indicate error
++
++ quint8 meiType = 0;
++ response.decodeData(&meiType);
++
++ // update size, header 6 bytes: mei type + read device id + conformity level + more follows
++ // + next object id + number of object
++ // response data part 2 bytes: + object id + object size of the first object -> 8
++ size = (meiType == EncapsulatedInterfaceTransport::ReadDeviceIdentification) ? 8 : minimum;
++ if (meiType != EncapsulatedInterfaceTransport::ReadDeviceIdentification
++ || response.dataSize() < size) {
++ break; // TODO: calculate CanOpenGeneralReference instead of break
++ }
++
++ const QByteArray data = response.data();
++ quint8 numOfObjects = quint8(data[5]);
++ quint8 objectSize = quint8(data[7]);
++
++ // 6 byte header size + (2 * n bytes fixed per object) + first object size
++ size = 6 + (2 * numOfObjects) + objectSize;
++ if ((numOfObjects == 1) || (data.size() < size))
++ break;
++
++ // header + object id + object size + second object id (9 bytes) + first object size
++ int nextSizeField = 9 + objectSize;
++ for (int i = 1; i < numOfObjects; ++i) {
++ if (data.size() <= nextSizeField)
++ break;
++ objectSize = quint8(data[nextSizeField]);
++ size += objectSize;
++ nextSizeField += objectSize + 2; // object size + object id field + object size field
++ }
++ } break;
++ default:
++ size = minimum;
++ break;
++ }
++ return size;
++ }
++
++ /*!
++ This function registers a user-defined implementation to calculate the
++ response data size for function code \a fc. It can be used to extend or
++ override the implementation inside \l QModbusResponse::calculateDataSize().
++
++ The \c CalcFuncPtr is a typedef for a pointer to a custom \a calculator
++ function with the following signature:
++ \code
++ int myCalculateDataSize(const QModbusResponse &pdu);
++ \endcode
++ */
++ void QModbusResponse::registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr calculator)
++ {
++ responseSizeCalculators()->insert(quint8(fc), calculator);
++ }
++
++ /*!
++ \relates QModbusResponse
++
++ Reads a \a pdu from the \a stream and returns a reference to the stream.
++
++ \note The function might fail to properly stream PDU's with function code
++ \l QModbusPdu::Diagnostics or \l QModbusPdu::EncapsulatedInterfaceTransport
++ because of the missing size indicator inside the PDU. In particular this may
++ happen when the PDU is embedded into a stream that doesn't end with the
++ diagnostic/encapsulated request itself.
++ */
++ QDataStream &operator>>(QDataStream &stream, QModbusResponse &pdu)
++ {
++ return QModbusPduPrivate::pduFromStream(stream, QModbusPduPrivate::Type::Response, &pdu);
++ }
++
++ /*!
++ \class QModbusExceptionResponse
++ \inmodule QtSerialBus
++ \since 5.8
++
++ \brief QModbusExceptionResponse is a container class containing the function and error code
++ inside a Modbus ADU.
++
++ A typical QModbusExceptionResponse response can looks like this:
++ \code
++ QModbusExceptionResponse exception(QModbusExceptionResponse::ReportServerId,
++ QModbusExceptionResponse::ServerDeviceFailure);
++ \endcode
++ */
++
++ /*!
++ \fn QModbusExceptionResponse::QModbusExceptionResponse()
++
++ Constructs an invalid QModbusExceptionResponse.
++ */
++
++ /*!
++ \fn QModbusExceptionResponse::QModbusExceptionResponse(const QModbusPdu &pdu)
++
++ Constructs a copy of \a pdu.
++ */
++
++ /*!
++ \fn QModbusExceptionResponse::QModbusExceptionResponse(FunctionCode code, ExceptionCode ec)
++
++ Constructs a QModbusExceptionResponse with function code set to \a code and exception error
++ code set to \a ec.
++ */
++
++ /*!
++ \internal
++ */
++ QModbusExceptionResponse::~QModbusExceptionResponse()
++ = default;
++
++ /*!
++ \fn void QModbusExceptionResponse::setFunctionCode(FunctionCode c)
++
++ Sets the response's function code to \a c.
++ */
++
++ /*!
++ \fn void QModbusExceptionResponse::setExceptionCode(ExceptionCode ec)
++
++ Sets the response's exception code to \a ec.
++ */
++
++ QT_END_NAMESPACE