1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtSerialBus module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#ifndef QMODBUSTCPSERVER_P_H
38#define QMODBUSTCPSERVER_P_H
39
40#include <QtCore/qdatastream.h>
41#include <QtCore/qdebug.h>
42#include <QtCore/qloggingcategory.h>
43#include <QtCore/qobject.h>
44#include <QtNetwork/qhostaddress.h>
45#include <QtNetwork/qtcpserver.h>
46#include <QtNetwork/qtcpsocket.h>
47#include <QtSerialBus/qmodbustcpserver.h>
48
49#include <private/qmodbusserver_p.h>
50
51#include <memory>
52
53//
54// W A R N I N G
55// -------------
56//
57// This file is not part of the Qt API. It exists purely as an
58// implementation detail. This header file may change from version to
59// version without notice, or even be removed.
60//
61// We mean it.
62//
63
64QT_BEGIN_NAMESPACE
65
66Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
67Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
68
69class QModbusTcpServerPrivate : public QModbusServerPrivate
70{
71 Q_DECLARE_PUBLIC(QModbusTcpServer)
72
73public:
74 /*
75 This function is a workaround since 2nd level lambda below cannot
76 call protected QModbusTcpServer::processRequest(..) function on VS2013.
77 */
78 QModbusResponse forwardProcessRequest(const QModbusRequest &r)
79 {
80 Q_Q(QModbusTcpServer);
81 if (q->value(option: QModbusServer::DeviceBusy).value<quint16>() == 0xffff) {
82 // If the device is busy, send an exception response without processing.
83 incrementCounter(counter: QModbusServerPrivate::Counter::ServerBusy);
84 return QModbusExceptionResponse(r.functionCode(),
85 QModbusExceptionResponse::ServerDeviceBusy);
86 }
87 return q->processRequest(request: r);
88 }
89
90 /*
91 This function is a workaround since 2nd level lambda below cannot
92 call protected QModbusDevice::setError(..) function on VS2013.
93 */
94 void forwardError(const QString &errorText, QModbusDevice::Error error)
95 {
96 Q_Q(QModbusTcpServer);
97 q->setError(errorText, error);
98 }
99
100 /*
101 This function is a workaround since 2nd level lambda below
102 cannot call QModbusServer::serverAddress(..) function on VS2013.
103 */
104 bool matchingServerAddress(quint8 unitId) const
105 {
106 Q_Q(const QModbusTcpServer);
107 if (q->serverAddress() == unitId)
108 return true;
109
110 // No, not our address! Ignore!
111 qCDebug(QT_MODBUS) << "(TCP server) Wrong server unit identifier address, expected"
112 << q->serverAddress() << "got" << unitId;
113 return false;
114 }
115
116 void setupTcpServer()
117 {
118 m_tcpServer = new QTcpServer(q_func());
119 QObject::connect(sender: m_tcpServer, signal: &QTcpServer::newConnection, context: q_func(), slot: [this]() {
120 Q_Q(QModbusTcpServer);
121 auto *socket = m_tcpServer->nextPendingConnection();
122 if (!socket)
123 return;
124
125 qCDebug(QT_MODBUS) << "(TCP server) Incoming socket from" << socket->peerAddress()
126 << socket->peerName() << socket->peerPort();
127
128 if (m_observer && !m_observer->acceptNewConnection(newClient: socket)) {
129 qCDebug(QT_MODBUS) << "(TCP server) Connection rejected by observer";
130 socket->close();
131 socket->deleteLater();
132 return;
133 }
134
135 connections.append(t: socket);
136
137 auto buffer = new QByteArray();
138
139 QObject::connect(sender: socket, signal: &QObject::destroyed, context: q, slot: [buffer]() {
140 // cleanup buffer
141 delete buffer;
142 });
143 QObject::connect(sender: socket, signal: &QTcpSocket::disconnected, context: q, slot: [socket, this]() {
144 connections.removeAll(t: socket);
145
146 Q_Q(QModbusTcpServer);
147 emit q->modbusClientDisconnected(modbusClient: socket);
148 socket->deleteLater();
149 });
150 QObject::connect(sender: socket, signal: &QTcpSocket::readyRead, context: q, slot: [buffer, socket, this]() {
151 if (!socket)
152 return;
153
154 buffer->append(a: socket->readAll());
155 while (!buffer->isEmpty()) {
156 qCDebug(QT_MODBUS_LOW).noquote() << "(TCP server) Read buffer: 0x"
157 + buffer->toHex();
158
159 if (buffer->size() < mbpaHeaderSize) {
160 qCDebug(QT_MODBUS) << "(TCP server) ADU too short. Waiting for more data.";
161 return;
162 }
163
164 quint8 unitId;
165 quint16 transactionId, bytesPdu, protocolId;
166 QDataStream input(*buffer);
167 input >> transactionId >> protocolId >> bytesPdu >> unitId;
168
169 qCDebug(QT_MODBUS_LOW) << "(TCP server) Request MBPA:" << "Transaction Id:"
170 << Qt::hex << transactionId << "Protocol Id:" << protocolId << "PDU bytes:"
171 << bytesPdu << "Unit Id:" << unitId;
172
173 // The length field is the byte count of the following fields, including the Unit
174 // Identifier and the PDU, so we remove on byte.
175 bytesPdu--;
176
177 if (buffer->size() < mbpaHeaderSize + bytesPdu) {
178 qCDebug(QT_MODBUS) << "(TCP server) PDU too short. Waiting for more data";
179 return;
180 }
181
182 QModbusRequest request;
183 input >> request;
184
185 buffer->remove(index: 0, len: mbpaHeaderSize + bytesPdu);
186
187 if (!matchingServerAddress(unitId))
188 continue;
189
190 qCDebug(QT_MODBUS) << "(TCP server) Request PDU:" << request;
191 const QModbusResponse response = forwardProcessRequest(r: request);
192 qCDebug(QT_MODBUS) << "(TCP server) Response PDU:" << response;
193
194 QByteArray result;
195 QDataStream output(&result, QIODevice::WriteOnly);
196 // The length field is the byte count of the following fields, including the Unit
197 // Identifier and PDU fields, so we add one byte to the response size.
198 output << transactionId << protocolId << quint16(response.size() + 1)
199 << unitId << response;
200
201 if (!socket->isOpen()) {
202 qCDebug(QT_MODBUS) << "(TCP server) Requesting socket has closed.";
203 forwardError(errorText: QModbusTcpServer::tr(s: "Requesting socket is closed"),
204 error: QModbusDevice::WriteError);
205 return;
206 }
207
208 qint64 writtenBytes = socket->write(data: result);
209 if (writtenBytes == -1 || writtenBytes < result.size()) {
210 qCDebug(QT_MODBUS) << "(TCP server) Cannot write requested response to socket.";
211 forwardError(errorText: QModbusTcpServer::tr(s: "Could not write response to client"),
212 error: QModbusDevice::WriteError);
213 }
214 }
215 });
216 });
217
218 QObject::connect(sender: m_tcpServer, signal: &QTcpServer::acceptError, context: q_func(),
219 slot: [this](QAbstractSocket::SocketError /*sError*/) {
220 Q_Q(QModbusTcpServer);
221
222 qCWarning(QT_MODBUS) << "(TCP server) Accept error";
223 q->setError(errorText: m_tcpServer->errorString(), error: QModbusDevice::ConnectionError);
224 });
225 }
226
227 QTcpServer *m_tcpServer { nullptr };
228 QVector<QTcpSocket *> connections;
229
230 std::unique_ptr<QModbusTcpConnectionObserver> m_observer;
231
232 static const qint8 mbpaHeaderSize = 7;
233 static const qint16 maxBytesModbusADU = 260;
234};
235
236QT_END_NAMESPACE
237
238#endif // QMODBUSTCPSERVER_P_H
239
240

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