Revision control

Copy as Markdown

// 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:critical reason:data-parser
#ifndef QMODBUSTCPSERVER_P_H
#define QMODBUSTCPSERVER_P_H
#include <QtCore/qdatastream.h>
#include <QtCore/qdebug.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qobject.h>
#include <QtNetwork/qhostaddress.h>
#include <QtNetwork/qtcpserver.h>
#include <QtNetwork/qtcpsocket.h>
#include <QtSerialBus/qmodbustcpserver.h>
#include <private/qmodbus_symbols_p.h>
#include <private/qmodbusserver_p.h>
#include <memory>
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
class QModbusTcpServerPrivate : public QModbusServerPrivate
{
Q_DECLARE_PUBLIC(QModbusTcpServer)
public:
/*
This function is a workaround since 2nd level lambda below cannot
call protected QModbusTcpServer::processRequest(..) function on VS2013.
*/
QModbusResponse forwardProcessRequest(const QModbusRequest &r)
{
Q_Q(QModbusTcpServer);
if (q->value(QModbusServer::DeviceBusy).value<quint16>() == 0xffff) {
// If the device is busy, send an exception response without processing.
incrementCounter(QModbusServerPrivate::Counter::ServerBusy);
return QModbusExceptionResponse(r.functionCode(),
QModbusExceptionResponse::ServerDeviceBusy);
}
return q->processRequest(r);
}
/*
This function is a workaround since 2nd level lambda below cannot
call protected QModbusDevice::setError(..) function on VS2013.
*/
void forwardError(const QString &errorText, QModbusDevice::Error error)
{
Q_Q(QModbusTcpServer);
q->setError(errorText, error);
}
/*
This function is a workaround since 2nd level lambda below
cannot call QModbusServer::serverAddress(..) function on VS2013.
*/
bool matchingServerAddress(quint8 unitId) const
{
Q_Q(const QModbusTcpServer);
if (q->serverAddress() == unitId)
return true;
// No, not our address! Ignore!
qCDebug(QT_MODBUS) << "(TCP server) Wrong server unit identifier address, expected"
<< q->serverAddress() << "got" << unitId;
return false;
}
void setupTcpServer()
{
m_tcpServer = new QTcpServer(q_func());
QObject::connect(m_tcpServer, &QTcpServer::newConnection, q_func(), [this]() {
Q_Q(QModbusTcpServer);
auto *socket = m_tcpServer->nextPendingConnection();
if (!socket)
return;
qCDebug(QT_MODBUS) << "(TCP server) Incoming socket from" << socket->peerAddress()
<< socket->peerName() << socket->peerPort();
if (m_observer && !m_observer->acceptNewConnection(socket)) {
qCDebug(QT_MODBUS) << "(TCP server) Connection rejected by observer";
socket->close();
socket->deleteLater();
return;
}
auto buffer = new QByteArray();
QObject::connect(socket, &QObject::destroyed, socket, [buffer]() {
// cleanup buffer
delete buffer;
});
QObject::connect(socket, &QTcpSocket::disconnected, q, [socket, this]() {
Q_Q(QModbusTcpServer);
emit q->modbusClientDisconnected(socket);
socket->deleteLater();
});
QObject::connect(socket, &QTcpSocket::readyRead, q, [buffer, socket, this]() {
if (!socket)
return;
buffer->append(socket->readAll());
while (!buffer->isEmpty()) {
qCDebug(QT_MODBUS_LOW).noquote() << "(TCP server) Read buffer: 0x"
+ buffer->toHex();
if (buffer->size() < mbapHeaderSize) {
qCDebug(QT_MODBUS) << "(TCP server) MBPA header too short. Waiting for more data.";
return;
}
quint8 unitId;
quint16 transactionId, bytesPdu, protocolId;
QDataStream input(*buffer);
input >> transactionId >> protocolId >> bytesPdu >> unitId;
qCDebug(QT_MODBUS_LOW) << "(TCP server) Request MBPA:" << "Transaction Id:"
<< Qt::hex << transactionId << "Protocol Id:" << protocolId << "PDU bytes:"
<< bytesPdu << "Unit Id:" << unitId;
const int current = mbapAduSize(bytesPdu);
if (!isValidAduSize(current)) {
// Resynchronizing would be guesswork, the next frame could start
// anywhere. Disconnect before reporting, the slot may delete socket.
qCWarning(QT_MODBUS) << "(TCP server) Invalid MBAP length field:"
<< bytesPdu << "closing connection.";
socket->disconnectFromHost();
forwardError(QModbusTcpServer::tr("Received invalid MBAP length field."),
QModbusDevice::ProtocolError);
return;
}
if (buffer->size() < current) {
qCDebug(QT_MODBUS) << "(TCP server) PDU too short. Waiting for more data";
return;
}
// Parse the PDU out of the declared frame only. QModbusPdu sizes its
// data from the function code and reads to the end of the device, so an
// unbounded stream lets it consume bytes belonging to the next frame.
QModbusRequest request;
QDataStream pdu(buffer->sliced(mbapHeaderSize, current - mbapHeaderSize));
pdu >> request;
buffer->remove(0, current);
if (!matchingServerAddress(unitId))
continue;
qCDebug(QT_MODBUS) << "(TCP server) Request PDU:" << request;
const QModbusResponse response = forwardProcessRequest(request);
qCDebug(QT_MODBUS) << "(TCP server) Response PDU:" << response;
QByteArray result;
QDataStream output(&result, QIODevice::WriteOnly);
// The length field is the byte count of the following fields, including the Unit
// Identifier and PDU fields, so we add one byte to the response size.
output << transactionId << protocolId << quint16(response.size() + 1)
<< unitId << response;
if (!socket->isOpen()) {
qCDebug(QT_MODBUS) << "(TCP server) Requesting socket has closed.";
forwardError(QModbusTcpServer::tr("Requesting socket is closed"),
QModbusDevice::WriteError);
return;
}
qint64 writtenBytes = socket->write(result);
if (writtenBytes == -1 || writtenBytes < result.size()) {
qCDebug(QT_MODBUS) << "(TCP server) Cannot write requested response to socket.";
forwardError(QModbusTcpServer::tr("Could not write response to client"),
QModbusDevice::WriteError);
}
}
});
});
QObject::connect(m_tcpServer, &QTcpServer::acceptError, q_func(),
[this](QAbstractSocket::SocketError /*sError*/) {
Q_Q(QModbusTcpServer);
qCWarning(QT_MODBUS) << "(TCP server) Accept error";
q->setError(m_tcpServer->errorString(), QModbusDevice::ConnectionError);
});
}
QTcpServer *m_tcpServer { nullptr };
std::unique_ptr<QModbusTcpConnectionObserver> m_observer;
};
QT_END_NAMESPACE
#endif // QMODBUSTCPSERVER_P_H