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
64 QT_BEGIN_NAMESPACE
65
Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)66 Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
67 Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
68
69 class QModbusTcpServerPrivate : public QModbusServerPrivate
70 {
71 Q_DECLARE_PUBLIC(QModbusTcpServer)
72
73 public:
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(QModbusServer::DeviceBusy).value<quint16>() == 0xffff) {
82 // If the device is busy, send an exception response without processing.
83 incrementCounter(QModbusServerPrivate::Counter::ServerBusy);
84 return QModbusExceptionResponse(r.functionCode(),
85 QModbusExceptionResponse::ServerDeviceBusy);
86 }
87 return q->processRequest(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(m_tcpServer, &QTcpServer::newConnection, q_func(), [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(socket)) {
129 qCDebug(QT_MODBUS) << "(TCP server) Connection rejected by observer";
130 socket->close();
131 socket->deleteLater();
132 return;
133 }
134
135 connections.append(socket);
136
137 auto buffer = new QByteArray();
138
139 QObject::connect(socket, &QObject::destroyed, q, [buffer]() {
140 // cleanup buffer
141 delete buffer;
142 });
143 QObject::connect(socket, &QTcpSocket::disconnected, q, [socket, this]() {
144 connections.removeAll(socket);
145
146 Q_Q(QModbusTcpServer);
147 emit q->modbusClientDisconnected(socket);
148 socket->deleteLater();
149 });
150 QObject::connect(socket, &QTcpSocket::readyRead, q, [buffer, socket, this]() {
151 if (!socket)
152 return;
153
154 buffer->append(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(0, mbpaHeaderSize + bytesPdu);
186
187 if (!matchingServerAddress(unitId))
188 continue;
189
190 qCDebug(QT_MODBUS) << "(TCP server) Request PDU:" << request;
191 const QModbusResponse response = forwardProcessRequest(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(QModbusTcpServer::tr("Requesting socket is closed"),
204 QModbusDevice::WriteError);
205 return;
206 }
207
208 qint64 writtenBytes = socket->write(result);
209 if (writtenBytes == -1 || writtenBytes < result.size()) {
210 qCDebug(QT_MODBUS) << "(TCP server) Cannot write requested response to socket.";
211 forwardError(QModbusTcpServer::tr("Could not write response to client"),
212 QModbusDevice::WriteError);
213 }
214 }
215 });
216 });
217
218 QObject::connect(m_tcpServer, &QTcpServer::acceptError, q_func(),
219 [this](QAbstractSocket::SocketError /*sError*/) {
220 Q_Q(QModbusTcpServer);
221
222 qCWarning(QT_MODBUS) << "(TCP server) Accept error";
223 q->setError(m_tcpServer->errorString(), 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
236 QT_END_NAMESPACE
237
238 #endif // QMODBUSTCPSERVER_P_H
239
240