1// Copyright (C) 2017 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// Qt-Security score:critical reason:data-parser
4
5#ifndef QMODBUSTCPSERVER_P_H
6#define QMODBUSTCPSERVER_P_H
7
8#include <QtCore/qdatastream.h>
9#include <QtCore/qdebug.h>
10#include <QtCore/qloggingcategory.h>
11#include <QtCore/qobject.h>
12#include <QtNetwork/qhostaddress.h>
13#include <QtNetwork/qtcpserver.h>
14#include <QtNetwork/qtcpsocket.h>
15#include <QtSerialBus/qmodbustcpserver.h>
16
17#include <private/qmodbusserver_p.h>
18
19#include <memory>
20
21//
22// W A R N I N G
23// -------------
24//
25// This file is not part of the Qt API. It exists purely as an
26// implementation detail. This header file may change from version to
27// version without notice, or even be removed.
28//
29// We mean it.
30//
31
32QT_BEGIN_NAMESPACE
33
34Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
35Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
36
37class QModbusTcpServerPrivate : public QModbusServerPrivate
38{
39 Q_DECLARE_PUBLIC(QModbusTcpServer)
40
41public:
42 /*
43 This function is a workaround since 2nd level lambda below cannot
44 call protected QModbusTcpServer::processRequest(..) function on VS2013.
45 */
46 QModbusResponse forwardProcessRequest(const QModbusRequest &r)
47 {
48 Q_Q(QModbusTcpServer);
49 if (q->value(option: QModbusServer::DeviceBusy).value<quint16>() == 0xffff) {
50 // If the device is busy, send an exception response without processing.
51 incrementCounter(counter: QModbusServerPrivate::Counter::ServerBusy);
52 return QModbusExceptionResponse(r.functionCode(),
53 QModbusExceptionResponse::ServerDeviceBusy);
54 }
55 return q->processRequest(request: r);
56 }
57
58 /*
59 This function is a workaround since 2nd level lambda below cannot
60 call protected QModbusDevice::setError(..) function on VS2013.
61 */
62 void forwardError(const QString &errorText, QModbusDevice::Error error)
63 {
64 Q_Q(QModbusTcpServer);
65 q->setError(errorText, error);
66 }
67
68 /*
69 This function is a workaround since 2nd level lambda below
70 cannot call QModbusServer::serverAddress(..) function on VS2013.
71 */
72 bool matchingServerAddress(quint8 unitId) const
73 {
74 Q_Q(const QModbusTcpServer);
75 if (q->serverAddress() == unitId)
76 return true;
77
78 // No, not our address! Ignore!
79 qCDebug(QT_MODBUS) << "(TCP server) Wrong server unit identifier address, expected"
80 << q->serverAddress() << "got" << unitId;
81 return false;
82 }
83
84 void setupTcpServer()
85 {
86 m_tcpServer = new QTcpServer(q_func());
87 QObject::connect(sender: m_tcpServer, signal: &QTcpServer::newConnection, context: q_func(), slot: [this]() {
88 Q_Q(QModbusTcpServer);
89 auto *socket = m_tcpServer->nextPendingConnection();
90 if (!socket)
91 return;
92
93 qCDebug(QT_MODBUS) << "(TCP server) Incoming socket from" << socket->peerAddress()
94 << socket->peerName() << socket->peerPort();
95
96 if (m_observer && !m_observer->acceptNewConnection(newClient: socket)) {
97 qCDebug(QT_MODBUS) << "(TCP server) Connection rejected by observer";
98 socket->close();
99 socket->deleteLater();
100 return;
101 }
102
103 auto buffer = new QByteArray();
104
105 QObject::connect(sender: socket, signal: &QObject::destroyed, context: socket, slot: [buffer]() {
106 // cleanup buffer
107 delete buffer;
108 });
109 QObject::connect(sender: socket, signal: &QTcpSocket::disconnected, context: q, slot: [socket, this]() {
110 Q_Q(QModbusTcpServer);
111 emit q->modbusClientDisconnected(modbusClient: socket);
112 socket->deleteLater();
113 });
114 QObject::connect(sender: socket, signal: &QTcpSocket::readyRead, context: q, slot: [buffer, socket, this]() {
115 if (!socket)
116 return;
117
118 buffer->append(a: socket->readAll());
119 while (!buffer->isEmpty()) {
120 qCDebug(QT_MODBUS_LOW).noquote() << "(TCP server) Read buffer: 0x"
121 + buffer->toHex();
122
123 if (buffer->size() < mbpaHeaderSize) {
124 qCDebug(QT_MODBUS) << "(TCP server) MBPA header too short. Waiting for more data.";
125 return;
126 }
127
128 quint8 unitId;
129 quint16 transactionId, bytesPdu, protocolId;
130 QDataStream input(*buffer);
131 input >> transactionId >> protocolId >> bytesPdu >> unitId;
132
133 qCDebug(QT_MODBUS_LOW) << "(TCP server) Request MBPA:" << "Transaction Id:"
134 << Qt::hex << transactionId << "Protocol Id:" << protocolId << "PDU bytes:"
135 << bytesPdu << "Unit Id:" << unitId;
136
137 // The length field is the byte count of the following fields, including the Unit
138 // Identifier and the PDU, so we remove on byte.
139 bytesPdu--;
140
141 const quint16 current = mbpaHeaderSize + bytesPdu;
142 if (buffer->size() < current) {
143 qCDebug(QT_MODBUS) << "(TCP server) PDU too short. Waiting for more data";
144 return;
145 }
146
147 QModbusRequest request;
148 input >> request;
149
150 buffer->remove(index: 0, len: current);
151
152 if (!matchingServerAddress(unitId))
153 continue;
154
155 qCDebug(QT_MODBUS) << "(TCP server) Request PDU:" << request;
156 const QModbusResponse response = forwardProcessRequest(r: request);
157 qCDebug(QT_MODBUS) << "(TCP server) Response PDU:" << response;
158
159 QByteArray result;
160 QDataStream output(&result, QIODevice::WriteOnly);
161 // The length field is the byte count of the following fields, including the Unit
162 // Identifier and PDU fields, so we add one byte to the response size.
163 output << transactionId << protocolId << quint16(response.size() + 1)
164 << unitId << response;
165
166 if (!socket->isOpen()) {
167 qCDebug(QT_MODBUS) << "(TCP server) Requesting socket has closed.";
168 forwardError(errorText: QModbusTcpServer::tr(s: "Requesting socket is closed"),
169 error: QModbusDevice::WriteError);
170 return;
171 }
172
173 qint64 writtenBytes = socket->write(data: result);
174 if (writtenBytes == -1 || writtenBytes < result.size()) {
175 qCDebug(QT_MODBUS) << "(TCP server) Cannot write requested response to socket.";
176 forwardError(errorText: QModbusTcpServer::tr(s: "Could not write response to client"),
177 error: QModbusDevice::WriteError);
178 }
179 }
180 });
181 });
182
183 QObject::connect(sender: m_tcpServer, signal: &QTcpServer::acceptError, context: q_func(),
184 slot: [this](QAbstractSocket::SocketError /*sError*/) {
185 Q_Q(QModbusTcpServer);
186
187 qCWarning(QT_MODBUS) << "(TCP server) Accept error";
188 q->setError(errorText: m_tcpServer->errorString(), error: QModbusDevice::ConnectionError);
189 });
190 }
191
192 QTcpServer *m_tcpServer { nullptr };
193
194 std::unique_ptr<QModbusTcpConnectionObserver> m_observer;
195
196 static const qint8 mbpaHeaderSize = 7;
197 static const qint16 maxBytesModbusADU = 260;
198};
199
200QT_END_NAMESPACE
201
202#endif // QMODBUSTCPSERVER_P_H
203
204

source code of qtserialbus/src/serialbus/qmodbustcpserver_p.h