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

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